Каковы некоторые общие сценарии, в которых делегаты должны использоваться?
Я понимаю, как работают делегаты и события. Я также могу представить некоторые общие сценарии, в которых мы должны внедрять события, но Im с более сложным пониманием, в каких ситуациях должны использоваться делегаты.
спасибо
ОТВЕТ НА ПОЛЬЗОВАТЕЛЮ KVB POST:
а)
В основном вы можете использовать делегаты, где бы вы ни использовали интерфейс с одним методом.
Думаю, я несколько понимаю следующее:
-
Класс C может определить метод C.M, который в качестве аргумента примет интерфейс IM. Этот интерфейс будет определять метод IM.A, и поэтому любому, кто хочет вызвать C.M, потребуется реализовать этот интерфейс.
-
В качестве альтернативы метод CM может принимать (вместо интерфейса IM) в качестве аргумента делегат D с той же сигнатурой как метод IM.A.
Но я не понимаю, почему Cant CM также использует в качестве параметра делегат D, даже если наш интерфейс IM определяет несколько других методов кроме того метод A? Таким образом, другие методы класса C могут потребовать в качестве аргумента интерфейса IM, но CM может потребоваться делегат D (при условии, что CM нужно только вызвать метод A, а не любой другой метод, определенный в IM)?
b)
var list = new List<int>(new[] { 1, 2, 3 });
var item = list.Find(i => i % 2 == 0);
-
Является ли приведенный выше код примером того, что пользователь вызывает jpbochi (см. ее/его сообщение в этой теме) инъекции зависимостей?
-
Я предполагаю, что указанный выше код не может быть реализован с использованием событий вместо "чистых" делегатов?
Ответы
Ответ 1
В основном вы можете использовать делегаты, где бы вы ни использовали интерфейс с одним методом. Хотя это не всегда уместно, часто читаемость значительно улучшается за счет использования делегатов вместо интерфейсов, поскольку логика поддерживается ближе к тому, где она используется. Например, какой из этих примеров легче понять и проверить правильность?
var list = new List<int>(new[] { 1, 2, 3 });
var item = list.Find(i => i % 2 == 0);
В отличие от:
var list = new List<int>(new[] { 1, 2, 3 });
list.Find(new DivisibleBy2Finder());
// Somewhere far away
private class DivisibleBy2Finder : IFinder<int> {
public bool Matches(int i) {
return i % 2 == 0;
}
}
UPDATE
Позвольте мне немного расширить свой ответ. Понятно, что делегаты очень похожи на однопоточные интерфейсы со специальным синтаксисом для вызова метода без использования его имени (т.е. С учетом делегата D
, вы можете вызвать его метод с помощью синтаксиса D()
). Есть еще две вещи, которые делают делегатов более интересными, чем интерфейсы с одним методом:
- Вы можете создать делегат из группы методов. Например, вы можете создать делегат
Action<string>
следующим образом: Action<string> action = new Action<string>(Console.WriteLine);
. Это создает делегат, который будет печатать свой аргумент на консоли, когда ему передается строка. Хотя это позволяет эффективно передавать методы, эти методы уже должны быть определены в каком-либо классе.
- Вы можете создать анонимный делегат. Для меня это ключевая причина, по которой делегаты особенно полезны в С#. Некоторые другие языки используют разные конструкции, чтобы инкапсулировать бит логики в своей точке использования (например, Java имеет анонимные классы). С# не имеет анонимных классов, поэтому, если вы хотите создать немного автономной логики, которая может быть передана другому методу, использование анонимного делегата (или нескольких анонимных делегатов) часто является лучшим подходом на С#. Это то, что я пытался проиллюстрировать с моим примером в моем первоначальном сообщении.
Отношения между событиями и делегатами немного сложны. Хотя верно, что события реализованы с точки зрения делегатов, я не уверен, что это лучший способ подумать о них. Делегаты, как и другие типы, могут использоваться во многих разных контекстах; они могут быть членами класса, они могут быть переданы в методы или возвращены из методов, они могут быть сохранены в локальных переменных внутри метода и т.д. События, с другой стороны, являются специальными членами класса, которые поддерживают следующие операции
- Делегаты могут быть добавлены или удалены из события. Эти делегаты будут вызываться, когда событие запускается/вызывается.
- Только в контексте класса, в котором объявлено событие, событие можно рассматривать как стандартный делегат, что означает, что он может быть вызван, и его список вызовов может быть проверен/обработан.
Таким образом, события часто отображаются на классах, чтобы позволить другим компонентам регистрировать обратные вызовы, которые будут вызваны из класса событий, когда это необходимо. Однако делегаты могут использоваться в гораздо более широком диапазоне ситуаций. Например, в библиотеке базового класса они часто используются в качестве аргументов для методов выполнения общих операций над коллекциями.
Надеюсь, что это поможет немного прояснить ситуацию.
Ответ 2
Если вы хотите предоставить функцию, которая будет выполнена на каком-либо событии. Вы передаете событию Event обработчик делегата функции, которая должна быть выполнена. Они великолепны, когда вы программируете на основе событий.
Также, когда у вас есть функции, которые имеют функции в качестве аргументов (выражения LINQ, предикаты, функции отображения, агрегированные функции и т.д.). Эти функции обычно называются функциями более высокого уровня.
Кроме того, вы можете обернуть некоторые функции, когда вызывающему нет необходимости обращаться к другим свойствам, методам или интерфейсам объекта, реализующего этот метод. В этом случае он каким-то образом заменяет наследование.
Ответ 3
На мой взгляд, делегаты - это самые простые средства инъекции зависимостей. Когда компонент получает делегат (либо в событии, либо в обычном методе), он позволяет другому компоненту вводить в него поведение.
Ответ 4
Реализация обратных вызовов и прослушивателей событий.
Например, если у вас есть функция, выполняющая удаленный запрос (например, получение списка ваших друзей в Facebook), вы можете передать делегат в качестве окончательного параметра этой функции и выполнить его после получения ответа сервера.
Ответ 5
асинхронные обратные вызовы - еще один хороший пример
Ответ 6
Делегаты используются для передачи функций сравнения в общие подпрограммы сортировки; делегаты могут использоваться для реализации шаблона стратегии; и делегаты используются для вызова асинхронных методов, среди прочих применений.
Отредактировано для добавления:
Этот тип сравнения между делегатами и событиями на самом деле не имеет никакого смысла на определенном уровне. Это похоже на то, почему нам нужны целые числа, когда мы могли бы пометить поля как "общедоступные".
An event
в С# является не чем иным, как ограничением доступа к полю типа делегата. В основном говорится, что другой класс или объект может получить доступ к полю для добавления и удаления, но не может проверять содержимое поля или делать изменения оптовых продаж в значение поля. Но тип события всегда является делегатом, и делегаты имеют много применений, которые не требуют ограничений доступа, которые предоставляет механизм событий.
Ответ 7
Я использую делегаты для хранения моих объектов и библиотек классов свободно в сочетании.
Например:
-
У меня есть два элемента управления TabControlA и TabControlB на MainForm. Их код находится в отдельных библиотеках, которые являются зависимостями MainForm.
-
TabControlA имеет общедоступный метод SetShowMessage, который устанавливает частный член с именем ShowMessage любому делегату (типа Action <string> , say).
-
Когда MainForm загружается, он может получить настройки, вызвав TabControlA.SetShowMessage(TabControlB.PrettyShowingFunction), чтобы подключить (только эту часть) TabControlB к (только та часть) TabControlA.
-
Теперь внутренне TabControlA может проверить, не является ли ShowMessage ненулевым, и вызвать ShowMessage ( "Hurray, сообщение, которое будет отображаться на TabControlB!" ), и теперь вызывается TabControlB.PrettyShowingFunction, позволяя TabControlA взаимодействовать с TabControlB, который может отображать это сообщение.
-
Это может быть расширено, чтобы TabControlC мог делать то же самое и показывать сообщения на TabControlB и т.д.
Я не знал, как это называется, но я думаю, что это шаблон посредника. Используя объекты "Медиатор", вы можете объединить еще больше делегатов, например метр прогресса и метку состояния на MainForm, которую любой элемент управления может обновить.
Ответ 8
Мне нравятся делегаты и события (они идут рука об руку) для разделения озабоченности (SOC).
Что такое делегат? Проще говоря, делегат является сигнатурой типа безопасного метода. События в основном хранят ссылку на набор методов... События и делегаты предоставляют способ предоставления уведомлений об изменении вне контекста нескольким потребителям...
События для издателей и подписчики получают уведомления.
Как это работает? Вот краткий пример.
Скажем, ваш код должен иметь действительный ввод перед обработкой заказа. В процедурном подходе ваш код (контроллер) может инициировать метод "порядок". Затем заказ проверяет, а затем отправляет или отклоняет...
В подходе издателя/подписчика могут быть следующие события OrderSubmitted, OrderValidated и OrderRejected. Это будут ваши издатели. Затем у вас есть несколько подписчиков, ValidateOrder, CommitOrder и RejectOrder... ValidateOrder будет подписаться на OrderSubmitted, CommitOrder подписывается на OrderValidated и, наконец, RejectOrder подписывается на OrderRejected.
В качестве параметра для ваших событий вы передаете заказ. Тогда серия событий будет...
Контроллер получает заказ. В коде предполагается, что проверка нулевого события имеет место...
void Init()
{
ValidateOrder += SomeValidateMethod;
CommitOrder += SomeCommitMethod;
RejectOrder += SomeRejectMethod;
}
void OrderReceived(Order o)
{
OrderEventArgs OEA = new OrderEventArgs(o);
ValidateOrder(this, OEA);
if (OEA.OrderIsValid)
CommitOrder(this, OEA);
else
RejectOrder(this, OEA);
}
Просто так, у нас есть некоторые события. Теперь, почему мы используем события/делегаты? Скажем, код для отклонения заказа обновляет базу данных, без проблем. Кто-то говорит, позвольте почтовому клиенту, когда заказ отклонен. Вам нужно реорганизовать SomeRejectMethod? Нет, вы можете просто создать новый метод EmailOrderRejected и добавить его в качестве подписчика на событие RejectOrder.
Это очень маленький пример, но он действительно помогает при использовании брокера событий для системы в целом. Это помогает отделить зависимости между методами...
Попытаюсь продолжить некоторые ссылки позже, удачи.
Ответ 9
Одна вещь, о которой я еще не упоминал, заключается в том, что делегаты упрощают хранение методов в структурах данных. Например, для меня не редкость найти что-то подобное в функциональном требовании:
Обновите все соответствующие записи предложений для этого платежа, установив дату своего статуса на дату события. Если плата распределяется с направлением перенаправления, соответствующими предложениями являются предложения с типом предложения "DV", "DCV" или "DVS". Если обвинение распределено с отложенным вводом решения, соответствующими предложениями являются предложения с типом предложения "DEJ". Игнорируйте все другие обвинения и предложения.
Одним из способов решения этой задачи было бы построить класс для предложений и класс для сборов и заполнить их из набора данных, а затем вставить все вышеперечисленные логики в методы. Другой способ - создать хороший большой набор вложенных условных выражений.
Третий способ, который в большей степени согласуется с наблюдением Стива Макконнелла о том, что проще отлаживать данные, чем код, заключается в определении таблицы поиска, содержащей предикаты для тестирования строк предложений:
private static readonly HashSet<string> DiversionTypes =
new HashSet() { "DV", "DCV", "DVS" };
private bool SentenceIsDiversion(DataRow r) { return (DiversionTypes.Contains(r.Field<string>("Type"))); }
private bool SentenceIsDEJ(DataRow r) { return r.Field<string>("Type") == "DEJ"; }
// Map charge disposition codes for diversion and DEJ to predicates that
// test sentence rows for relevance. Only sentences for charges whose disposition
// code is in this map and who are described by the related predicate should be
// updated.
private static readonly Dictionary<string, Func<DataRow, bool>> DispoToPredicateMap =
new Dictionary<string, Func<DataRow, bool>>
{
{ "411211", SentenceIsDiversion },
{ "411212", SentenceIsDiversion },
{ "411213", SentenceIsDEJ },
{ "411214", SentenceIsDEJ },
}
Что делает логику обновления выглядеть так:
string disposition = chargeRow.Field<string>("Disposition");
if (DispoToPredicateMap.ContainsKey(disposition))
{
foreach (DataRow sentenceRow in chargeRow.GetChildRows("FK_Sentence_Charge"))
{
if (DispoToPredicateMap[disposition](sentenceRow))
{
sentenceRow.SetField("StatusDate", eventDate);
}
}
}
Из трех подходов это самое сложное в первую очередь (или понять, если вы не знакомы с этой техникой). Но гораздо проще писать модульные тесты, которые покрывают 100% кода, и его легко обновлять, когда условия запуска также меняются.