Комментарии 60
Спасибо, для новичков очень познавательно.
+4
Поправьте пожалуйста имена по CamelCase стандарту: Handler1 вместо Handler_I и OnCount вместо onCount для события. А то новичкам такое вредно показывать.
+12
Запомните! Если Вы не подписались на событие и его делегат пустой, возникнет ошибка.
Чтобы избежать этого, необходимо подписаться, или не вызывать событие вообще, как показано на примере (Т.к. событие — делегат, то его отсутствие является «нулевой ссылкой» null).
Пример, сам по себе, является образцом как не нужно делать, между проверкой и вызовом, может произойти отписывание от события и тогда получим NullReferenceException. Выходов из этой ситуации ровно два. Первый — копируем событие в локальную переменную и работаем с ней, второй — в месте инициализации класса добавляем пустой обработчик для события, исключая ситуацию, когда на него никто не подписан и оно равно null.
+7
Полностью солидарен с вашим замечанием. Внесу свои 5 копеек для прояснения ситуации:
После такой простой инициализации события в конструкторе уже можно не бояться словить NullReferenceException.
class ClassCounter //Это класс - в котором производится счет.
{
...
...
...
//Событие OnCount c типом делегата MethodContainer.
public event MethodContainer onCount;
...
...
...
public ClassCounter ()
{
onCount = () => { };
}
}
После такой простой инициализации события в конструкторе уже можно не бояться словить NullReferenceException.
+4
Многие прогрессивные посоны объявляют события так:
public event MethodContainer OnCount = delegate{};
+1
Или даже вот так:
public event Action OnCount = delegate{};
+1
В таком случае можно использовать в дополнение к проверке на null еще и try catch, чтобы исключить такую возможность. Не лежит у меня душа к созданию сущности ( delegate{} ), что не будет задействована в логике выполнения программы )
0
Если не сложно, поясните, пожалуйста, эту «магию» —
onCount = () => { };
Что происходит в этой строке?
onCount = () => { };
Что происходит в этой строке?
0
Создание анонимной функции на основе лямбда-выражения и создание на ее основе делегата подходящего типа.
+1
() => {} — это анонимный метод. Они необходимы в тех случаях, когда программисту незачем париться над именами методов и вызываться они будут, как правило в одном единственном месте. Пустыми круглыми скобками мы сообщаем компилятору, что наш анонимный метод не будет иметь параметров. Анонимные методы всегда имеют тип возврата void. Как правило цепочка события-делегаты-анонимные методы образуют единое звено, применение одного без другого мало чем может быть полезна. Такая нотация очень удобна, не приходится дробить логику класса на дополнительное объявление методов.
По большому счету onCount = () => { }; эквивалентна следующей:
…
…
void Method1()
{
// здеся пустота!
}
…
…
…
onCount = Method1;
Согласитесь, что первый вариант гораздо компактнее и удобнее? Не согласились?) Верю, новичкам немного сложно привыкнуть к ним. Советую почитать в интернете побольше на данную тематику статей, тема немаленькая, но интересная и очень часто в жизни это может пригодиться
По большому счету onCount = () => { }; эквивалентна следующей:
…
…
void Method1()
{
// здеся пустота!
}
…
…
…
onCount = Method1;
Согласитесь, что первый вариант гораздо компактнее и удобнее? Не согласились?) Верю, новичкам немного сложно привыкнуть к ним. Советую почитать в интернете побольше на данную тематику статей, тема немаленькая, но интересная и очень часто в жизни это может пригодиться
+5
public void fun(){}
0
() => {}
это сокращенная запись для
delegate { }
ох блин, уже ответили.
это сокращенная запись для
delegate { }
ох блин, уже ответили.
0
Xored, если Вы начинающий, то лямбда для Вас, скорее всего лишнее. Уважаемый kin9pin, не плохо объяснил «что это». Огромное ему спасибо.
0
Отлично разжевано для новичков!
Примите совет: названия событий не должны начинаться с On… По принятой в .NET системе именования с On… начинаются названия методов, которые эти самые события генерят, например OnTextChanged генерирует событие TextChanged.
Примите совет: названия событий не должны начинаться с On… По принятой в .NET системе именования с On… начинаются названия методов, которые эти самые события генерят, например OnTextChanged генерирует событие TextChanged.
+9
Спасибо за статью. А не могли бы вы еще рассказать про возможные утечки памяти при работе с событиями и про то, как с ними бороться?
+1
В двух словах не расскажешь, ув. dzigoro. Может этого стоит отдельная статья. Но в двух словах: контролируйте время жизни циклов, методов, переменных. А также области видимости, чтобы «спецы» не поломали Ваш проект. А они не любят порой разбираться в Вашем коде.
0
Если временный (маложивущий) объект подпишется на событие долгоживущего объекта и забудет отписаться, то он останется в памяти до сборки долгоживущего. Его будет держать подписка. Это наиболее частый случай утечки.
+2
Многие разработчики утверждают (и я с ними согласен), что главная проблема «недопонимания» событий — их специфическая область применения, а вследствие — мало доступных примеров. Ну и не забывайте о практике
Ну если вы являетесь веб разработчиком, то скорее всего не встретите. Мне на практике не приходилось.
Но вот если вы разрабатываете на WPF/Silverlight, то вам гарантированно придется работать с делегатами и эвентами. Ну если конечно вы не пишите банальные «hello, world» и rss фиды.
Ну если вы являетесь веб разработчиком, то скорее всего не встретите. Мне на практике не приходилось.
Но вот если вы разрабатываете на WPF/Silverlight, то вам гарантированно придется работать с делегатами и эвентами. Ну если конечно вы не пишите банальные «hello, world» и rss фиды.
0
Спасибо за статью, для новичков самое оно.
Но, считаю, что не раскрыта два важных аспекта:
1) метод GetInvocationList зачем он нужен
2) конструкция
Но, считаю, что не раскрыта два важных аспекта:
1) метод GetInvocationList зачем он нужен
2) конструкция
public event EventHandler Changed
class Class {
EventHandler сhanged;
public event EventHandler Changed {
add {
changed += value;
}
remove {
chnaged -=value;
}
}
}
+1
А также не раскрыта разница между
и
поскольку в обоих случаях можно подписаться на событие, но ключевое слово
Кроме того неплохо бы описать для новичков наличие стандартных делегатов
public event EventHandler Changed
и
public EventHandler Changed
поскольку в обоих случаях можно подписаться на событие, но ключевое слово
event
вносит свои тонкостиКроме того неплохо бы описать для новичков наличие стандартных делегатов
Func<>
и Action<>
+3
Ув. urrri. Я писал статью с минимальным погружением в .NET. Это базовый уровень.
-1
Ну уж разница между использование и неиспользованием ключевого слова это совсем основа. Иначе вы рассказали не о эвенте а о делегате — в вашем коде можно спокойно убрать все слова
event
и ничего не изменится+1
Помните, очень важно не только подписываться от событий, но и отписываться потом. События хранят ссылки на объекты подписки и пренебрежение отписыванием делает невозможной работу мусорщика. Часто бывает, что делают одно событие которое живет долго и подписывают на него кучу короткоживущих объектов. Не будете отписываться после вызова — вся эта котовасия начнет вам забивать оперативную память.
+1
Либо использовать слабые связи, если невозможно определить, когда нужно отписываться от события www.codeproject.com/Articles/29922/Weak-Events-in-C
+2
Тема, пожалуй, полезная, но слишком уж поверхностно раскрыта. В комментариях, в целом все проблемы озвучены:
Итого, как мне кажется, для совсем новичков информации маловато, а для более-менее ушедших от «новичков» — недостаточно.
- возможность null-reference
- не упомянуты операции подписки add/remove
- не упомянуты удобные helper-ы в виде EventHandler и EventHandler(T);
Итого, как мне кажется, для совсем новичков информации маловато, а для более-менее ушедших от «новичков» — недостаточно.
+2
Если Вы пишете статью для новичков — пожалуйста, упомянтье о майкросотвовских стандартах для сигнатур и именовании методов, кидающих события и самих событий и приведите код в примерах к этим стандартам.
+1
Я не хотел в этой статье употреблять ни стандарты, ни «фишки» .NET-a, ничего лишнего. Программист все равно за определенный промежуток времени приобретет свой стиль и научится писать код, как ему удобно (или как на его конторе хотят). У каждого в голове за столько лет — свой стандарт.
-1
Ключевое слово event — это исключительно фигка C#, его к сожалению нету в питоне, джаве, плюсах и многих других меинстримовых языках. Если Вам хотелось рассказать про общий паттерн Observer, то имело смысл отвязаться как от слова event, так и от делегатов, так как анонимных методов тоже много где нет.
В таком виде, как оно представлено в посте, оно применимо только для C#, поэтому лучше использовать общепринятые соглашения для этого языка.
В таком виде, как оно представлено в посте, оно применимо только для C#, поэтому лучше использовать общепринятые соглашения для этого языка.
0
Как показывает практика, мало кому в голову приходит, почему EventHandler имеет такую странную сигнатуру, и зачем он вообще нужен, если есть Action. А ведь он такой не зря, просто его достоинства не слишком очевидны. Это я к чему, настоятельно рекомендовал бы начинающим писать события так, как предложено умными дядьками из Microsoft, а ещё лучше разобраться, почему они так предлагают.
+1
Для полноты можно указать ещё одну статью про события, которая уже была на хабре.
+1
На мой взгляд, новичку гораздо проще понять события, если вообще забить на эти попытки абстрагирования и просто научиться воспринимать функцию как сущность, с которой можно работать как с обычной переменной — присваивать ее, передавать как аргумент и т.д. Тем более, что для современного шарпа это понимание важно и практично. Ведь вся суть событий сводится к тому, что функция куда-то передается и потом вызывается уже в том месте.
0
Не сочтите за пиар, но для более полного понимания/углублённого изучения я бы посоветовал эту статью.
+1
Решили похвастаться в статье для новичков? Аплодисменты в студию.
0
А что кривого в событиях C#? Опишите чем конкретно недовольно прогрессивное человечество
0
А что кривого в событиях C#? Опишите чем конкретно недовольно прогрессивное человечество
События в .NET являются синтаксическим сахаром для паттерна Observer. Причём сахаром довольно никчемным, потому что упрощения от них чуть, а ограничения весьма существенные.
Как правило среди наблюдателей (observers) и наблюдаемого (observable) большим сроком жизни (областью видимости) обладает последний. Это значит, что в их взаимодействии критически важной становится отписка от событий. В противном случае мы сталкиваемся с утечками памяти — подвисанием короткоживущих объектов, которые после своего использования недоступны сборщику мусора, даже если отсутствуют прямые ссылки на них в пользовательском коде (поскольку продолжают оставаться неявные ссылки из источника событий).
В нормальных реализациях паттерна Observer для отписки используются токены подписки (subscriptions), желательно совместимые с идиомой RAII (отписка через деструкторы в C++ или через IDisposable в C#). Именно так это сделано в Boost.Signals2 в C++ или в Rx в C#. Правильный синтаксический сахар в C# должен был выглядеть так (псевдокод):
private IDisposable _subscription; ... _subscription = publisher.SomeEvents += subscriber.HandleSomeEvent; ... _subscription.Dispose();
Токен подписки не требует тащить всю дорогу явную ссылку на обработчик, чтобы отписаться. Но в C# последнее сделано иначе:
publisher.SomeEvents -= subscriber.HandleSomeEvent;
В частности, это значит, что при подписке нельзя так просто взять и использовать лямбды, скажем, обернуть предыдущий вызов:
publisher.SomeEvents += (sender, e) => { Debug.Log(“Some message.”); subscriber.HandleSomeEvent(sender, e); }
потому что:
publisher.SomeEvents -= ???
Обычно observers и observable друг о друге ничего не знают, а подписку инициирует какая-то третья сторона (назовём её «менеджер»).
Так вот, в случае правильной реализации паттерна Наблюдатель, менеджеру не надо тащить за собой ссылки на подписчиков или источник событий. Скажем, менеджер получил в конструкторе один раз ссылки на подписчиков и издателя, связал их друг с другом и полностью забыл про них:
_subscription = observable.SomeEvents.Subscribe(observer.HandleSomeEvent);
Для того, чтобы отписаться, ему не нужны ни подписчик, ни источник, достаточно безликого поля IDisposable:
_subscription.Dispose();
В случае отписки от событий C# всё сильно хуже, зачем-то нужны и подписчик, и издатель:
observable.SomeEvent -= observer.HandleSomeEvent;
То есть безосновательно увеличена связность.
Выше уже писали про кривой синтаксис возбуждения событий (raise events). В отсутствие подписчиков (зауряднейшая ситуация) вместо простого «ничего» получаем на ровном месте `NullReferenceException`. Причём нельзя просто так взять, и проверить на `null`, как в статье, потому что между проверкой и возбуждением события может отписаться последний подписчик, и вместо ожидаемого пустого invokation list получаем `null`; выше уже указывали на это.
Далее, события в C# не могут сигнализировать об окончании потока `OnComplete()` или об ошибке `OnError(exception)`. Не могут на лету комбинироваться и преобразовываться Linq-методами. Не предусмотрено библиотечного механизма для разделения «холодных» и «горячих» потоков событий, буферизующих, и т.д.
+2
Внушает. После такого фундаментального комментария осталось только написать про то, как эти проблемы решены в упомянутом Rx.
+1
Как-то мне кажется дизайн приложения хромает. Событие onCount должно бы файриться на каждой итерации цикла со значением i в EventArgs, а уже сами подписчики должны решать, что им с этим чудом делать. :)
0
Недавно начал изучать C# после многих лет на плюсах. И вот, обнаруживаю, что в C# существует стандартный механизм сигналов, очень для меня полезный. Но вдруг выясняется, что я не могу просто привязать некий объект к событиям и заставить его отписаться от событий во время его уничтожения без каких-то дополнительных действий, воспользовавшись этим стандартным механизмом. А как же RAII? Видимо, я что-то не понимаю в концепции языка.
В каких случаях чаще всего используются события с C#? Кто контролирует отписку?
В каких случаях чаще всего используются события с C#? Кто контролирует отписку?
0
> А как же RAII? Видимо, я что-то не понимаю в концепции языка.
Аналогом плюсового деструктора в C# является метод `Dispose()` интерфейса `IDisposable` (не путать с тильдой-финализатором). RAII в C# реализуется через паттерн Disposable. Аналогом плюсового локального скоупа является синтаксис `using (var foo = new SomeDisposable()) {… }` — метод `foo.Dispose()` вызовется при покидании скоупа при достижении конца, `return` или исключении.
> Кто контролирует отписку?
Для контролируемой отписки а-ля RAII как в boost::signals2 проще использовать не стандартные события, а Rx, см. мой комментарий выше.
> В каких случаях чаще всего используются события с C#?
Сырые события .NET удобно использовать в ситуациях, когда время жизни подписчика заведомо не меньше времени жизни источника событий, тогда отписка не нужна. При желании можно вручную сконструировать «токен подписки», `Dispose()` которого вызовет отписку:
EventHandler<MyArgs> handler = (sender, e) => { Debug.Log(“Some message.”); subscriber.DoSomething(e.SomeProperty, e.AnotherProperty); }
publisher.SomeEvent += handler;
// Замыкаем подписчика и источник событий в безликом `Action`:
Action actionToBeCalledOnDispose = () => { publisher.SomeEvent -= handler; }
_subscription = Disposable.Create(actionToBeCalledOnDispose);
Аналогом плюсового деструктора в C# является метод `Dispose()` интерфейса `IDisposable` (не путать с тильдой-финализатором). RAII в C# реализуется через паттерн Disposable. Аналогом плюсового локального скоупа является синтаксис `using (var foo = new SomeDisposable()) {… }` — метод `foo.Dispose()` вызовется при покидании скоупа при достижении конца, `return` или исключении.
> Кто контролирует отписку?
Для контролируемой отписки а-ля RAII как в boost::signals2 проще использовать не стандартные события, а Rx, см. мой комментарий выше.
> В каких случаях чаще всего используются события с C#?
Сырые события .NET удобно использовать в ситуациях, когда время жизни подписчика заведомо не меньше времени жизни источника событий, тогда отписка не нужна. При желании можно вручную сконструировать «токен подписки», `Dispose()` которого вызовет отписку:
EventHandler<MyArgs> handler = (sender, e) => { Debug.Log(“Some message.”); subscriber.DoSomething(e.SomeProperty, e.AnotherProperty); }
publisher.SomeEvent += handler;
// Замыкаем подписчика и источник событий в безликом `Action`:
Action actionToBeCalledOnDispose = () => { publisher.SomeEvent -= handler; }
_subscription = Disposable.Create(actionToBeCalledOnDispose);
+1
Просто, доступно понятно. Пускай полежит у меня в избранном
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
События C# по-человечески