событие Action <> против события EventHandler <> - PullRequest
129 голосов
/ 16 сентября 2009

Есть ли разница между объявлением 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 <> слишком много аргументов типа, тогда потребуется дополнительный класс.

Ответы [ 6 ]

82 голосов
/ 28 ноября 2011

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

Во-первых, физические ограничения использования Action<T1, T2, T2... > против использования производного класса EventArgs. Их три: во-первых, если вы изменяете количество или типы параметров, каждый метод, на который подписывается, должен быть изменен, чтобы соответствовать новому шаблону. Если это общедоступное событие, которое будут использовать сторонние сборки, и есть вероятность того, что аргументы события будут изменены, это будет причиной для использования пользовательского класса, полученного из аргументов события, для согласованности (помните, что вы МОЖЕТЕ еще используйте Action<MyCustomClass>) Во-вторых, использование Action<T1, T2, T2... > предотвратит передачу обратной связи BACK вызывающему методу, если у вас нет какого-либо объекта (например, со свойством Handled), который передается вместе с действием. В-третьих, вы не получаете именованные параметры, поэтому, если вы передаете 3 bool, int, два string и DateTime, вы не знаете, что означают эти значения являются. В качестве дополнительного примечания вы можете использовать метод «Безопасное выполнение этого события при использовании Action<T1, T2, T2... >».

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

В-третьих, в реальной жизни я лично обнаружил, что склонен создавать множество разовых событий для таких вещей, как изменения свойств, с которыми мне нужно взаимодействовать (особенно при работе с MVVM с моделями представления, которые взаимодействуют друг с другом) или где событие имеет один параметр. В большинстве случаев эти события принимают форму public event Action<[classtype], bool> [PropertyName]Changed; или public event Action SomethingHappened;. В этих случаях есть два преимущества. Сначала я получаю тип для класса выдачи. Если MyClass объявляет и является единственным классом, запускающим событие, я получаю явный экземпляр MyClass для работы в обработчике событий. Во-вторых, для простых событий, таких как события изменения свойств, значение параметров очевидно и указано в имени обработчика событий, и мне не нужно создавать множество классов для событий такого типа.

66 голосов
/ 16 сентября 2009

Основным отличием будет то, что если вы используете Action<>, ваше событие не будет соответствовать схеме проектирования практически любого другого события в системе, что я бы назвал недостатком.

Одним из преимуществ доминирующего шаблона проектирования (помимо силы одинаковости) является то, что вы можете расширить объект EventArgs новыми свойствами, не изменяя сигнатуру события. Это все еще было бы возможно, если бы вы использовали Action<SomeClassWithProperties>, но я не вижу смысла в том, чтобы не использовать обычный подход в этом случае.

15 голосов
/ 16 сентября 2009

По большей части, я бы сказал, следуйте шаблону. У меня есть отклонение от него, но очень редко и по конкретным причинам. В данном случае самая большая проблема, с которой я столкнулся, заключается в том, что я, вероятно, все еще буду использовать Action<SomeObjectType>, что позволит мне позже добавить дополнительные свойства и использовать случайное двустороннее свойство (например, Handled или другие события обратной связи, в которых абонент должен установить свойство объекта события). И как только вы начнете в том же духе, вы можете использовать EventHandler<T> для некоторых T.

14 голосов
/ 02 ноября 2011

Преимущество более сложного подхода возникает, когда ваш код находится внутри проекта с 300 000 строк.

Используя действие, как у вас, невозможно сказать мне, что такое bool, int и Blah. Если ваше действие прошло объект, который определил параметры, тогда хорошо.

Используя EventHandler, который хотел EventArgs, и если вы завершите свой пример DiagnosticsArgs с получателями для свойств, которые комментируют их назначение, тогда ваше приложение будет более понятным. Также, пожалуйста, прокомментируйте или полностью назовите аргументы в конструкторе DiagnosticsArgs.

6 голосов
/ 18 сентября 2009

Если вы следуете стандартному шаблону событий, вы можете добавить метод расширения, чтобы сделать проверку срабатывания событий более безопасной / легкой. (т. е. в следующем коде добавлен метод расширения 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();
    }
}
2 голосов
/ 20 сентября 2017

Глядя на Стандартные шаблоны событий .NET Мы находим

Стандартная подпись делегата события .NET:

void OnEventRaised(object sender, EventArgs args);

[...]

Список аргументов содержит два аргумента: отправитель и аргументы события. Тип отправителя времени компиляции - System.Object, даже если вы, вероятно, знаете, более производный тип, который всегда будет корректным. По соглашению используйте object .

Ниже на той же странице мы находим пример определения типичного события, что-то вроде

public event EventHandler<EventArgs> EventName;

Если бы мы определили

class MyClass
{
  public event Action<MyClass, EventArgs> EventName;
}

обработчик мог быть

void OnEventRaised(MyClass sender, EventArgs args);

, где sender имеет правильный ( более производный ) тип.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...