Событие Действие <> против события EventHandler <>
Есть ли разница между объявлением event Action<>
и event EventHandler<>
.
Предполагая, что не имеет значения, какой объект действительно вызвал событие.
например:
public event Action<bool, int, Blah> DiagnosticsEvent;
против
public event EventHandler<DiagnosticsArgs> DiagnosticsEvent;
class DiagnosticsArgs : EventArgs
{
public DiagnosticsArgs(bool b, int i, Blah bl)
{...}
...
}
В обоих случаях использование
будет почти одинаковым:
obj.DiagnosticsEvent += HandleDiagnosticsEvent;
Есть несколько вещей, которые мне не нравятся в шаблоне event EventHandler<>
:
- Объявление дополнительного типа, полученное из
EventArgs
- Принудительное прохождение источника объекта -
часто никто не заботится
Дополнительный код означает больше кода для поддержки без каких-либо явных преимуществ.
В результате я предпочитаю event Action<>
Однако, только если в Action < > слишком много аргументов типа, тогда потребуется дополнительный класс.
Ответы
Ответ 1
Основное отличие будет заключаться в том, что если вы используете Action<>
, ваше событие не будет следовать шаблону проектирования практически любого другого события в системе, что я считаю недостатком.
Один верх с доминирующим шаблоном проектирования (помимо силы однообразия) заключается в том, что вы можете расширить объект EventArgs
новыми свойствами без изменения сигнатуры события. Это было бы возможно, если вы использовали Action<SomeClassWithProperties>
, но я действительно не вижу смысла не использовать регулярный подход в этом случае.
Ответ 2
Основываясь на некоторых предыдущих ответах, я собираюсь разбить свой ответ на три области.
Во-первых, физические ограничения использования Action<T1, T2, T2... >
vs с использованием производного класса EventArgs
. Есть три: во-первых, если вы измените число или типы параметров, каждый метод, который подписывается, должен быть изменен, чтобы соответствовать новому шаблону. Если это публичное событие, которое будут использовать сторонние сборки, и есть вероятность, что переменные событий будут меняться, это может послужить основанием для использования пользовательского класса, полученного из аргументов событий для согласованности (помните, что вы ДОЛЖНЫ используйте Action<MyCustomClass>
). Во-вторых, использование Action<T1, T2, T2... >
не позволит вам передать обратную связь BACK к вызывающему методу, если у вас нет какого-либо объекта (например, с использованием свойства Handled), который передается вместе с действием. В-третьих, вы не получаете именованных параметров, поэтому, если вы передаете 3 bool
a int
, два string
и a DateTime
, вы не знаете, что означает эти значения. В качестве побочного примечания вы все равно можете безопасно использовать "Огонь этого события" при использовании Action<T1, T2, T2... >
".
Во-вторых, последствия последовательности. Если у вас есть большая система, с которой вы уже работаете, почти всегда лучше следить за тем, как разрабатывается остальная часть системы, если у вас не очень хорошая причина. Если вы публично сталкиваетесь с событиями, которые необходимо поддерживать, возможность замены производных классов может быть важна. Помните об этом.
В-третьих, реальная практика жизни, я лично считаю, что я, как правило, создаю много событий для вещей, таких как изменения свойств, с которыми мне нужно взаимодействовать (особенно при работе с MVVM с моделями просмотра, которые взаимодействуют друг с другом) или где событие имеет один параметр. Большую часть времени эти события принимают форму public event Action<[classtype], bool> [PropertyName]Changed;
или public event Action SomethingHappened;
. В этих случаях есть два преимущества. Во-первых, я получаю тип для класса выдачи. Если MyClass
объявляет и является единственным классом, запускающим событие, я получаю явный экземпляр MyClass
для работы с обработчиком события. Во-вторых, для простых событий, таких как события изменения свойств, значение параметров очевидно и указано в имени обработчика событий, и мне не нужно создавать множество классов для этих событий.
Ответ 3
По большей части, я бы сказал, следуйте шаблону. Я отклонился от него, но очень редко, и по определенным причинам. В этом случае самая большая проблема, с которой я столкнулся, это то, что я, вероятно, все еще буду использовать Action<SomeObjectType>
, что позволит мне добавить дополнительные свойства позже и использовать случайное двухстороннее свойство (подумайте Handled
, или другие события обратной связи, в которых абоненту необходимо установить свойство объекта события). И как только вы запустили эту строку, вы можете использовать EventHandler<T>
для некоторых T
.
Ответ 4
Преимущество подхода Worder заключается в том, что ваш код находится внутри проекта в 300 000 строк.
Используя действие, как и у вас, нет способа рассказать мне, что такое bool, int и Blah. Если ваше действие передало объект, который определил параметры, тогда ok.
Используя EventHandler, который хотел EventArgs, и если вы завершили бы ваш пример DiagnosticsArgs с помощью getters для свойств, которые прокомментировали их назначение, то ваше приложение было бы более понятным. Кроме того, прокомментируйте или полностью укажите аргументы в конструкторе DiagnosticsArgs.
Ответ 5
Если вы следуете стандартным шаблонам событий, вы можете добавить метод расширения, чтобы сделать проверку срабатывания событий более безопасной/легкой. (т.е. следующий код добавляет метод расширения, называемый SafeFire(), который выполняет нулевую проверку, а также (очевидно) копирование события в отдельную переменную, чтобы быть в безопасности от обычного состояния гонки, которое может повлиять на события.)
(Хотя я имею в виду два ума, следует ли использовать методы расширения для нулевых объектов...)
public static class EventFirer
{
public static void SafeFire<TEventArgs>(this EventHandler<TEventArgs> theEvent, object obj, TEventArgs theEventArgs)
where TEventArgs : EventArgs
{
if (theEvent != null)
theEvent(obj, theEventArgs);
}
}
class MyEventArgs : EventArgs
{
// Blah, blah, blah...
}
class UseSafeEventFirer
{
event EventHandler<MyEventArgs> MyEvent;
void DemoSafeFire()
{
MyEvent.SafeFire(this, new MyEventArgs());
}
static void Main(string[] args)
{
var x = new UseSafeEventFirer();
Console.WriteLine("Null:");
x.DemoSafeFire();
Console.WriteLine();
x.MyEvent += delegate { Console.WriteLine("Hello, World!"); };
Console.WriteLine("Not null:");
x.DemoSafeFire();
}
}
Ответ 6
Глядя на Стандартные шаблоны событий .NET, мы находим
Стандартная подпись для делегата .NET event:
void OnEventRaised(object sender, EventArgs args);
[...]
Список аргументов содержит два аргумента: отправитель и аргументы события. Тип времени компиляции отправителя - System.Object, хотя вы, вероятно, знаете более производный тип, который всегда был бы правильным. По соглашению используйте объект.
Ниже на той же странице мы находим пример типичного определения события, что-то вроде
public event EventHandler<EventArgs> EventName;
Если бы мы определили
class MyClass
{
public event Action<MyClass, EventArgs> EventName;
}
обработчик мог бы быть
void OnEventRaised(MyClass sender, EventArgs args);
где sender
имеет правильный (более производный) тип.