Создание событий в C# с использованием обобщений (generics)

{lang: 'ru'}

События в C# используются повсеместно и многие знакомятся с ними в самом начале изучения этого языка. В этом посте я не буду рассказывать основы и базовые принципы создания событий в C# и в .NET. Вместо этого, я расскажу, как создать обобщения (generic) для событий.

К вопросу создания дженерика для событий я пришёл из-за работы над дипломом, где мне нужно создавать много событий, отличающихся только передаваемыми аргументами. При этом, логика их создания и вызова была идентична. Но аргументы могли быть различными, причём в будущем их разнообразие должно только увеличиваться. На помощь пришёл всемогущий Google и всезнающий Рихтер.

Как известно, .NET уже содержит обобщение (generic) для делегата EventHandler

Но TEventArgs должен получаться из EventArgs. Т.е., если вы хотите, чтобы событие содержало int, код должен быть вроде такого:

Далее, если станет нужна строка, то понадобится писать так:

Вам нужен отдельный класс, наследуемый от EventArgs для каждого типа объекта, который вы хотите передать с событием. Либо, придётся передавать данные с типом object:

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

Создадим класс, наследуемый от EventArgs с обобщённым типом:

Теперь можно создать класс, который будем передавать с событием:


Теперь создадим обработчик события используя только что определённый класс (SimpleObject) или любой другой класс:

Подписка на событие и его обработка стандартны:

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

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

В этом случае публикация события будет выглядеть так:

К сожалению, метод Volatile.Read доступен только начиная с .NET Framework 4.5. Из-за этого его не получится использовать при разработке на Unity3d, т.к. он до сих пор использует старую версию Mono, совместимую только с .NET Framework 3.5

По старой доброй традиции, публикую архив с работающим примером кода.


При подготовке поста использовались материалы codeproject и книги Джефри Рихтера «CLR via C#» 4-го издания.


Полезная статья? Их будет больше, если вы поддержите меня!

C#