Когда использовать обратные вызовы вместо событий в с #?

Простите меня, если это глупый вопрос, признаюсь, я не особо задумывался над этим.

Но когда вы предпочтете использовать обратный вызов (например, передачу Func или Action), а не раскрывать и использовать событие?

ОБНОВЛЕНИЕ

Причиной этого вопроса была следующая проблема:

У меня есть класс ThingsHandler, который можно связать с ThingEditor. ThingsHandler обрабатывает список вещей, знает их порядок, какой из них является «текущим», когда новые добавляются или удаляются и т. Д.

ThingEditors могут изменять только одну вещь.

ThingsHandler должен предупреждать ThingEditor, когда пользователь выбирает новую вещь для редактирования, а ThingEditor должен предупреждать ThingsHandler, когда пользователь говорит «готово».

Что меня беспокоит, так это то, что эти два класса содержат ссылки друг на друга - хотя я думаю, что это неизбежно - или привязаны к событиям в обоих направлениях. Я задавался вопросом, было ли «чище» использование обратного вызова в одном направлении.

Я подозреваю, что для этого есть шаблон дизайна, и смиренно прошу прощения за свое невежество (и лень).


person Benjol    schedule 07.01.2010    source источник


Ответы (8)


Хотя другие ответы пока кажутся разумными, я бы выбрал более философский подход.

Класс - это механизм, который моделирует определенный тип вещей в определенной области. При написании внутренних деталей класса очень легко объединить детали реализации механизма с моделируемой семантикой. Краткий пример того, что я имею в виду:

class Giraffe : Mammal, IDisposable
{
    public override void Eat(Food f) { ... }
    public void Dispose() { ... }
}

Обратите внимание, как мы объединили моделируемую вещь из реального мира (жираф - это разновидность млекопитающего, жираф ест еду) с деталями реализации (экземпляр Giraffe - это объект, от которого можно избавиться с помощью " утверждение). Я гарантирую, что если вы пойдете в зоопарк, вы никогда не увидите жирафа, от которого избавляются с помощью инструкции using. Мы перепутали уровни здесь, что очень прискорбно.

Я стараюсь использовать события (и свойства) как часть семантической модели и использую методы обратного вызова (и поля) как часть механизма. Я бы сделал GaveBirth событием Giraffe, поскольку это часть модели поведения жирафа в реальном мире, которую мы пытаемся запечатлеть. Если бы у меня был какой-то механизм, например, я хотел бы реализовать алгоритм обхода дерева в порядке обхода, который обходил генеалогическое древо жирафов и вызывал метод для каждого, тогда я бы сказал, что это явно механизм, а не часть модели и сделайте это обратным вызовом, а не пытайтесь впихнуть это в модель событий.

person Eric Lippert    schedule 07.01.2010
comment
Черт, ты снова заставляешь мой мозг болеть :) В моем текущем коде классы так далеки от всего в реальном мире, я не уверен, с чего бы начать ... (см. Обновленный вопрос) - person Benjol; 08.01.2010
comment
Хороший ответ, и подведем итог: события - это уведомления (родила), обратные вызовы - это запросы (съесть еду). Связав это с реальным миром, вы получите диаграмму вариантов использования с жирафом в качестве объекта и пользователем в качестве обработчика животных (или ребенок с попкорном, бросающим его через решетку). - person Codesleuth; 08.01.2010
comment
Не только сложно переварить, но и сложно перевести ... В конце концов, оно того стоит! Спасибо, Эрик. - person Yair Nevet; 10.12.2013
comment
Я думаю, что тот факт, что вам не разрешено назначать null или метод для события, находящегося за пределами класса, который предоставляет событие, в то время как вам разрешено делать это внутри, является важным доказательством того, что объяснил Эрик Липперт. Разрешено только добавление и удаление методов. - person Bronek; 15.06.2016

Я использую обратные вызовы в нескольких случаях, когда я знаю, что он будет только когда-либо срабатывать только один раз, а обратный вызов специфичен для одного вызова метода (а не для экземпляра объекта) - например, как возвращаемая часть асинхронного метода.

Это особенно верно в отношении статических служебных методов (поскольку у вас нет экземпляра, а статические события смертельны при неосторожном использовании, и их следует избегать), но, конечно, другой вариант - вместо этого создать экземпляр класса с событием.

person Marc Gravell    schedule 07.01.2010

Обычно я использую обратный вызов, если он требуется, тогда как событие используется, когда оно должно быть необязательным. Не раскрывайте событие, если вы ожидаете, что всегда будет что-то слушать.

Учтите следующее:

public class MyClass_Event
{
    public event EventHandler MakeMeDoWork;

    public void DoWork()
    {
        if (MakeMeDoWork == null)
            throw new Exception("Set the event MakeMeDoWork before calling this method.");
        MakeMeDoWork(this, EventArgs.Empty);
    }
}

против:

public class MyClass_Callback
{
    public void DoWork(EventHandler callback)
    {
        if (callback == null)
            throw new ArgumentException("Set the callback.", "callback"); // better design
        callback(this, EventArgs.Empty);
    }
}

Код почти такой же, как обратный вызов может быть передан как null, но, по крайней мере, выброшенное исключение может быть более актуальным.

person Codesleuth    schedule 07.01.2010

Обратные вызовы хороши, когда один объект хочет получить одно уведомление (например, выполняется чтение данных Async, а затем вызывает вас с результатом).

События хороши для повторяющихся уведомлений, которые могут быть получены произвольным числом слушателей.

person Jason Williams    schedule 07.01.2010

С точки зрения объектно-ориентированного проектирования и связывания классов нет большой разницы между интерфейсом обратного вызова и событием.

Однако я предпочитаю события, в которых класс должен «кричать» всем, кто заинтересован в прослушивании (обычно несколько вещей), и обратные вызовы, когда конкретный класс запросил асинхронную операцию.

Что бы вы ни использовали, используйте их последовательно во всей кодовой базе!

person Paolo    schedule 07.01.2010

Я бы использовал Func или Action, когда собираюсь вызвать функцию один раз или использовать лямбда-выражение.

События могут быть зарегистрированы более одного раза, что иногда бывает идеально. При обратном вызове необходимо реализовать систему регистрации для обратных вызовов, если вы хотите, чтобы их было несколько.

person Daniel A. White    schedule 07.01.2010

Один из примеров - когда обратный вызов должен что-то возвращать. Например. (глупый пример):

public int Sum(Func<int> callbackA, Func<int> callbackB) {
    return callbackA() + callbackB();
}

public void UseSum() {
    return sum(() => 10, () => 20);
}
person erikkallen    schedule 07.01.2010

Что ж, я думаю, это одно и то же. Существует множество различных технических терминов для обозначения одних и тех же концепций или вещей на разных языках.

Итак, что вы имеете в виду обратный вызов или обработчик событий?

Согласно MSDN: функция обратного вызова код в управляемом приложении, который помогает неуправляемой функции DLL выполнить задачу.

Кроме того, MADN дает представление о различиях между ними. нажмите здесь

Обратные вызовы - это точки расширяемости, которые позволяют платформе выполнять обратный вызов в пользовательский код через делегата. Эти делегаты обычно передаются фреймворку через параметр метода.

События - это особый случай обратных вызовов, который поддерживает удобный и согласованный синтаксис для предоставления делегата (обработчика событий). Кроме того, автозавершение операторов и конструкторы Visual Studio помогают в использовании API-интерфейсов на основе событий.

Кроме того, в некоторых книгах, например эту книгу, автор, похоже, сказал то же самое с MSDN.

Поэтому, на мой взгляд, в C # нельзя использовать обратные вызовы вместо событий.

person Alex Xu    schedule 18.10.2015
comment
Обратный вызов - это не зависящая от языка идея передачи functionA в качестве аргумента функцииB. Когда в функции B выполняются определенные условия, будет вызвана функция A. Это другое понятие, чем события. - person Josh Noe; 26.03.2019