События C# по-человечески


Невозможно, просто взять и вникнуть в этот глубокий смысл, изучая События (event) в просторах базового и, на первый взгляд, бесконечного C#.

Когда я изучал События (не в рамках .NET!), потратил много сил, чтобы, наконец-то, разобраться, как они устроены и должны конструироваться. Поэтому, я решил опубликовать свою методику понимания структуры пользовательского события, коим представляется ключевое слово event в С#.
Не буду цитировать и без того замученную MSDN, а постараюсь объяснить понятно и доступно.


Что требуется Вам для изучения:
  • 1. Вы не должны испытывать страх перед изучением. Пожалуйста, читайте медленно и вдумчиво.
  • 2. Вы должны понимать классы и методы.
  • 3. Вы должны знать понимать, что есть делегаты. Хотя, Вы можете попытаться понять их в ходе чтения статьи.

Событие, это не что иное, как ситуация, при возникновении которой, произойдет действие или несколько действий. Говоря языком программного моделирования, Событие — это именованный делегат, при вызове которого, будут запущены все подписавшиеся на момент вызова события методы заданной сигнатуры. Эта трактовка хоть и раскрывает всю суть структуры события, но не только сбивает с толку начинающих «шарп-прогеров», но и не дает возможность рационально представить в программистской голове весь смысл.

Итак, Событие, это ситуация, при возникновении которой, произойдут некоторые действия. Само событие имеет определенную структуру.

Предположим, что стоит такая задача: определено три класса. Первый класс будет считать до 100, используя цикл. Два других класса будут ждать, когда в первом классе счетчик досчитает, например, до 71, и после этого каждый выведет в консоль фразу «Пора действовать, ведь уже 71!». Проще говоря, при обнаружении значения 71, вызовутся по методу, соответственно для каждого класса. Разложим все по полкам.

1. Моделирование ситуации.

Подготовим эти три простейших класса, оставив точку входа в программу main нетронутой.
Класс ClassCounter и его метод Count() в котором будет производится счет. (В коде я опускаю пространства имен namespace, ибо это ясно, как день).

    class ClassCounter  //Это класс - в котором производится счет.
    {
        public void Count()
        {
            //Здесь будет производиться счет
        }
    }

Два других класса (имена им Handler_I и Handler_II), которые должны реагировать на возникновение события методами public void Message(). У каждого по методу, как и договаривались.

    class Handler_I //Это класс, реагирующий на событие (счет равен 71) записью строки в консоли.
    {
        public void Message()
        {
            //Не забудьте using System 
            //для вывода в консольном приложении
            Console.WriteLine("Пора действовать, ведь уже 71!"); 
        }                                                        
    }

    class Handler_II
    {
        public void Message()
        {
            Console.WriteLine("Точно, уже 71!");
        }    
    }

Напомню, когда счетчик будет считать до 100 и достигнет 71, должны сработать методы Message() для классов Handler_I и Handler_II.
Теперь вернемся к классу ClassCounter и создадим счетчик при помощи цикла for c переменной-счетчиком int i.

    class ClassCounter  //Это класс - в котором производится счет.
    {
        public void Count()
        {
            for (int i = 0; i < 100; i++)
            {
            }
        }
    }

Первый этап завершен. У нас есть класс счетчик и два класса, которые будут выводить сообщения. Условия задачи: как только i=71, должны сработать методы Message() для двух классов Handler_I и Handler_II.

2. Оформление события.

Абстрагируемся от программирования. Событие, которое мы хотим создать, будет представлять фразу "… счетчик считает. И как только он будет равен 71, должны выполниться действия". Значит, нам необходимо условие «как только он будет равен 71». Представим его при помощи условного оператора if.

    class ClassCounter  //Это класс - в котором производится счет.
    {
        public void Count()
        {
            for (int i = 0; i < 100; i++)
            {
                if (i == 71)
                {
                }
            }
        }
    }

Конструируем событие event. Определяем по методам, которые должны сработать при i=71 их сигнатуру (или прототип).
Сигнатура метода — это так называемая спецификация (или простыми словами «шаблон») какого-л. метода или методов. Представляет собой сочетание названия типа, который метод возвращает, плюс название типов входящих параметров (по порядку! порядок очень важен.)
Например, метод int NewMethod(int x, char y) будет иметь сигнатуру int (int, char), а метод void NewMethod()void (void).
Как толкует MSDN, события (event) основаны на делегатах (delegate), а делегат, говоря очень простым языком — «переменная, хранящая ссылку на метод». Как Вы уже поняли, т.к. наше событие будет ссылаться на два метода void Message(), мы должны определить сигнатуру этих методов, и составить на основе этой сигнатуры делегат. Сигнатура выглядит так: void (void).

Определяем делегат (назовем его MethodContainer):

    class ClassCounter  //Это класс - в котором производится счет.
    {
        //Синтаксис по сигнатуре метода, на который мы создаем делегат: 
        //delegate <выходной тип> ИмяДелегата(<тип входных параметров>);
        //Мы создаем на void Message(). Он должен запуститься, когда условие выполнится.

        public delegate void MethodContainer(); 

        public void Count()
        {
            for (int i = 0; i < 100; i++)
            {
                if (i == 71)
                {
                }
            }
        }
    }

Далее, мы создаем событие при помощи ключевого слова event и связываем его с этим делегатом (MethodContainer), а, следовательно, c методами, имеющими сигнатуру void (void). Событие должно быть public, т.к. его должны использовать разные классы, которым нужно как-то отреагировать (классы Handler_I и Handler_II).
Событие имеет синтаксис: public event <НазваниеДелегата> <НазваниеСобытия>;
Название делегата — это имя делегата, на который «ссылаются» наши методы.

 class ClassCounter  //Это класс - в котором производится счет.
    {
        public delegate void MethodContainer();

        //Событие OnCount c типом делегата MethodContainer.
        public event MethodContainer onCount;

        public void Count()
        {
            for (int i = 0; i < 100; i++)
            {
                if (i == 71)
                {
                }
            }
        }
    }

Теперь запустим наше событие onCount, в условии когда i=71:

if (i == 71)
{
     onCount();
}

Все. Событие создано. Методы, которые вызовет это событие, определены по сигнатурам и на основе их создан делегат. Событие, в свою очередь, создано на основе делегата. Пора показать событию onCount, какие же все-таки методы должны сработать (мы ведь указали только их сигнатуру).

3. Подписка.

Вернемся в точку входа программы main и создадим экземпляр класса ClassCounter. А также создадим по экземпляру классов, которые должны запуститься. (Они должны быть public).

    class Program
    {
        static void Main(string[] args)
        {
            ClassCounter Counter = new ClassCounter();
            Handler_I Handler1 = new Handler_I();
            Handler_II Handler2 = new Handler_II();
        }
    }

Теперь укажем событию onCount, методы, которые должны запуститься.
Происходит это следующим образом: <КлассИлиОбъект>.<ИмяСобытия> += <КлассЧейМетодДолженЗапуститься>.<МетодПодходящийПоСигнатуре>.
Никаких скобочек после метода! Мы же не вызываем его, а просто указываем его название.

    class Program
    {
        static void Main(string[] args)
        {
            ClassCounter Counter = new ClassCounter();
            Handler_I Handler1 = new Handler_I();
            Handler_II Handler2 = new Handler_II();

            //Подписались на событие
            Counter.onCount += Handler1.Message;
            Counter.onCount += Handler2.Message;
        }
    }


Проверка.

Теперь осталось запустить счетчик класса ClassCounter и подождать, пока i станет равным 71. Как только i=71, запустится событие onCount по делегату MethodContainer, который (в свою очередь) запустит методы Message(), которые были подписаны на событие.

    class Program
    {
        static void Main(string[] args)
        {
            ClassCounter Counter = new ClassCounter();
            Handler_I Handler1 = new Handler_I();
            Handler_II Handler2 = new Handler_II();

            Counter.onCount += Handler1.Message;
            Counter.onCount += Handler2.Message;

            //Запустили счетчик
            Counter.Count();
        }
    }

Результат:
Пора действовать, ведь уже 71!
Точно, уже 71!

Заключение.

Постарайтесь понять смысл и порядок создания события.
  • 1. Определите условие возникновения события и методы которые должны сработать.
  • 2. Определите сигнатуру этих методов и создайте делегат на основе этой сигнатуры.
  • 3. Создайте общедоступное событие на основе этого делегата и вызовите, когда условие сработает.
  • 4. Обязательно (где-угодно) подпишитесь на это событие теми методами, которые должны сработать и сигнатуры которых подходят к делегату.

Класс, в котором вы создаете событие (генерируете) называется классом-издателем, а классы, чьи методы подписываются на это событие при помощи "+=" — классами-подписчиками.

Запомните! Если Вы не подписались на событие и его делегат пустой, возникнет ошибка.
Чтобы избежать этого, необходимо подписаться, или не вызывать событие вообще, как показано на примере (Т.к. событие — делегат, то его отсутствие является «нулевой ссылкой» null).

                if (i == 71)
                {
                    if (onCount != null)
                    {
                        onCount();
                    } 
                }

Вы всегда можете отписаться, используя оператор "-=": <КлассИлиОбъект>.<ИмяСобытия> -= <КлассЧейМетодДолженЗапуститься>.<МетодПодходящийПоСигнатуре>.

Преимущество Событий очевидно: классу-издателю, генерирующему событие не нужно знать, сколько классов-подписчиков подпишется или отпишется. Он создал событие для определенных методов, ограничив их делегатом по определенной сигнатуре.
События широко используются для составления собственных компонентов управления (кнопок, панелей, и т.д.).

У самых маленьких может возникнуть вопрос: что делать, если методы, которые должны сработать имеют входящий параметр (а то и не один!)?
Ответ: Все дело в делегате, на котором базируется событие. А точнее сигнатура подходящих для делегата методов. Когда Вы сконструируете делегат, «принимающий» метод с параметром, то (!) при запуске событие запросит этот параметр. Естественно, параметр может быть чем угодно.

Пару слов о .NET-событиях. Microsoft упростила задачу конструирования делегатов: .NET предлагает готовый делегат EventHandler и т.н. «пакет» входных параметров EventArgs. Желаете событие? Берете готовый EventHandler, определяетесь в параметрах, «запихиваете» их в класс, а класс наследуете от EventArgs. А дальше — как по расписанию)

Многие разработчики утверждают (и я с ними согласен), что главная проблема «недопонимания» событий — их специфическая область применения, а вследствие — мало доступных примеров. Ну и не забывайте о практике.

P.S. Если вы не ни разу не использовали делегаты, лучше попробуйте потренироваться на делегатах, а затем попытайтесь понять эту статью.
Я надеюсь, что внес небольшое понимание в эту непростую тему. Успехов!
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 60

    +4
    Спасибо, для новичков очень познавательно.
      0
      Благодарю
        0
        Действительно, для новичков может быть полезно. Однако, автор забыл описать классы Handler_I и Handler_II и их реализации методов Message(). Там будет все просто, но для новичков это может быть не так то уж и очевидно.
          0
          Они в самом начале описаны.
            0
            Прошу прощения, пропустил. Читал статью, а думал больше о проекте (текущая задача адовая).
        +12
        Поправьте пожалуйста имена по CamelCase стандарту: Handler1 вместо Handler_I и OnCount вместо onCount для события. А то новичкам такое вредно показывать.
        +7
        Запомните! Если Вы не подписались на событие и его делегат пустой, возникнет ошибка.
        Чтобы избежать этого, необходимо подписаться, или не вызывать событие вообще, как показано на примере (Т.к. событие — делегат, то его отсутствие является «нулевой ссылкой» null).


        Пример, сам по себе, является образцом как не нужно делать, между проверкой и вызовом, может произойти отписывание от события и тогда получим NullReferenceException. Выходов из этой ситуации ровно два. Первый — копируем событие в локальную переменную и работаем с ней, второй — в месте инициализации класса добавляем пустой обработчик для события, исключая ситуацию, когда на него никто не подписан и оно равно null.
          +4
          Полностью солидарен с вашим замечанием. Внесу свои 5 копеек для прояснения ситуации:

          class ClassCounter  //Это класс - в котором производится счет.
          {
                  ...
                  ...
                  ...
                  //Событие OnCount c типом делегата MethodContainer.
                  public event MethodContainer onCount;
                  ...
                  ...
                  ...
                  public ClassCounter ()
                  {
                      onCount = () => { };
                  }
          }
          

          После такой простой инициализации события в конструкторе уже можно не бояться словить NullReferenceException.
            +1
            Многие прогрессивные посоны объявляют события так:

            public event MethodContainer OnCount = delegate{};
              +1
              Или даже вот так:

              public event Action OnCount = delegate{};
              
                –2
                Статья-то не для прогрессивных)))
                  0
                  А может даже так:
                  public event EventHandler MyEvent = delegate { }; 
                  
                  0
                  В таком случае можно использовать в дополнение к проверке на null еще и try catch, чтобы исключить такую возможность. Не лежит у меня душа к созданию сущности ( delegate{} ), что не будет задействована в логике выполнения программы )
              0
              Если не сложно, поясните, пожалуйста, эту «магию» —
              onCount = () => { };
              Что происходит в этой строке?
                +1
                Создание анонимной функции на основе лямбда-выражения и создание на ее основе делегата подходящего типа.
                  +5
                  () => {} — это анонимный метод. Они необходимы в тех случаях, когда программисту незачем париться над именами методов и вызываться они будут, как правило в одном единственном месте. Пустыми круглыми скобками мы сообщаем компилятору, что наш анонимный метод не будет иметь параметров. Анонимные методы всегда имеют тип возврата void. Как правило цепочка события-делегаты-анонимные методы образуют единое звено, применение одного без другого мало чем может быть полезна. Такая нотация очень удобна, не приходится дробить логику класса на дополнительное объявление методов.
                  По большому счету onCount = () => { }; эквивалентна следующей:




                  void Method1()
                  {
                  // здеся пустота!
                  }




                  onCount = Method1;

                  Согласитесь, что первый вариант гораздо компактнее и удобнее? Не согласились?) Верю, новичкам немного сложно привыкнуть к ним. Советую почитать в интернете побольше на данную тематику статей, тема немаленькая, но интересная и очень часто в жизни это может пригодиться
                    0
                    public void fun(){}
                      0
                      () => {}
                      это сокращенная запись для
                      delegate { }

                      ох блин, уже ответили.
                        0
                        Xored, если Вы начинающий, то лямбда для Вас, скорее всего лишнее. Уважаемый kin9pin, не плохо объяснил «что это». Огромное ему спасибо.
                        +9
                        Отлично разжевано для новичков!

                        Примите совет: названия событий не должны начинаться с On… По принятой в .NET системе именования с On… начинаются названия методов, которые эти самые события генерят, например OnTextChanged генерирует событие TextChanged.
                          +1
                          Не грех будет добавить что если следовать Framework Guidelines эти самые OnXXX методы должны быть virtual, как минимум protected для не-sealed классов и по возможности не содержать большого кол-ва логики кроме вызова события.
                          +1
                          Спасибо за статью. А не могли бы вы еще рассказать про возможные утечки памяти при работе с событиями и про то, как с ними бороться?
                            0
                            В двух словах не расскажешь, ув. dzigoro. Может этого стоит отдельная статья. Но в двух словах: контролируйте время жизни циклов, методов, переменных. А также области видимости, чтобы «спецы» не поломали Ваш проект. А они не любят порой разбираться в Вашем коде.
                              +2
                              Если временный (маложивущий) объект подпишется на событие долгоживущего объекта и забудет отписаться, то он останется в памяти до сборки долгоживущего. Его будет держать подписка. Это наиболее частый случай утечки.

                              0
                              Многие разработчики утверждают (и я с ними согласен), что главная проблема «недопонимания» событий — их специфическая область применения, а вследствие — мало доступных примеров. Ну и не забывайте о практике

                              Ну если вы являетесь веб разработчиком, то скорее всего не встретите. Мне на практике не приходилось.
                              Но вот если вы разрабатываете на WPF/Silverlight, то вам гарантированно придется работать с делегатами и эвентами. Ну если конечно вы не пишите банальные «hello, world» и rss фиды.
                                +1
                                Спасибо за статью, для новичков самое оно.
                                Но, считаю, что не раскрыта два важных аспекта:
                                1) метод GetInvocationList зачем он нужен
                                2) конструкция public event EventHandler Changed

                                class Class {
                                EventHandler сhanged;
                                public event EventHandler Changed {
                                add {
                                changed += value;
                                }
                                remove {
                                chnaged -=value;
                                }
                                }
                                }
                                  +3
                                  А также не раскрыта разница между
                                  public event EventHandler Changed
                                  и
                                  public EventHandler Changed
                                  поскольку в обоих случаях можно подписаться на событие, но ключевое слово event вносит свои тонкости

                                  Кроме того неплохо бы описать для новичков наличие стандартных делегатов Func<> и Action<>
                                    –1
                                    Ув. urrri. Я писал статью с минимальным погружением в .NET. Это базовый уровень.
                                      +1
                                      Ну уж разница между использование и неиспользованием ключевого слова это совсем основа. Иначе вы рассказали не о эвенте а о делегате — в вашем коде можно спокойно убрать все слова event и ничего не изменится
                                        +1
                                        Я имел в виду Вашу просьбу об описании Func<> и Action<>. Не путайте базовый C# и базовый .NET, основанный на С#.
                                  +1
                                  Помните, очень важно не только подписываться от событий, но и отписываться потом. События хранят ссылки на объекты подписки и пренебрежение отписыванием делает невозможной работу мусорщика. Часто бывает, что делают одно событие которое живет долго и подписывают на него кучу короткоживущих объектов. Не будете отписываться после вызова — вся эта котовасия начнет вам забивать оперативную память.
                                  +2
                                  Тема, пожалуй, полезная, но слишком уж поверхностно раскрыта. В комментариях, в целом все проблемы озвучены:
                                  • возможность null-reference
                                  • не упомянуты операции подписки add/remove
                                  • не упомянуты удобные helper-ы в виде EventHandler и EventHandler(T);

                                  Итого, как мне кажется, для совсем новичков информации маловато, а для более-менее ушедших от «новичков» — недостаточно.
                                    +1
                                    Тут самое главное не написано — в чем, собственно, разница между событием и полем или свойством делегатного типа.
                                    +1
                                    Если Вы пишете статью для новичков — пожалуйста, упомянтье о майкросотвовских стандартах для сигнатур и именовании методов, кидающих события и самих событий и приведите код в примерах к этим стандартам.
                                      –1
                                      Я не хотел в этой статье употреблять ни стандарты, ни «фишки» .NET-a, ничего лишнего. Программист все равно за определенный промежуток времени приобретет свой стиль и научится писать код, как ему удобно (или как на его конторе хотят). У каждого в голове за столько лет — свой стандарт.
                                        0
                                        Ключевое слово event — это исключительно фигка C#, его к сожалению нету в питоне, джаве, плюсах и многих других меинстримовых языках. Если Вам хотелось рассказать про общий паттерн Observer, то имело смысл отвязаться как от слова event, так и от делегатов, так как анонимных методов тоже много где нет.
                                        В таком виде, как оно представлено в посте, оно применимо только для C#, поэтому лучше использовать общепринятые соглашения для этого языка.
                                          +1
                                          Как показывает практика, мало кому в голову приходит, почему EventHandler имеет такую странную сигнатуру, и зачем он вообще нужен, если есть Action. А ведь он такой не зря, просто его достоинства не слишком очевидны. Это я к чему, настоятельно рекомендовал бы начинающим писать события так, как предложено умными дядьками из Microsoft, а ещё лучше разобраться, почему они так предлагают.
                                        +1
                                        Для полноты можно указать ещё одну статью про события, которая уже была на хабре.
                                          0
                                          Да, вот это действительно отличная статья по событиям. Понятная и довольно полная.
                                            0
                                            Спасибо.
                                              –1
                                              Если что, то я похвалил не вашу статью, а ту, что приведена в ссылке выше.
                                                +1
                                                Понятно было и без пояснения
                                          0
                                          На мой взгляд, новичку гораздо проще понять события, если вообще забить на эти попытки абстрагирования и просто научиться воспринимать функцию как сущность, с которой можно работать как с обычной переменной — присваивать ее, передавать как аргумент и т.д. Тем более, что для современного шарпа это понимание важно и практично. Ведь вся суть событий сводится к тому, что функция куда-то передается и потом вызывается уже в том месте.
                                            0
                                            Согласен. А также многие программисты были бы еще лучше, если бы понимали суть программирования в целом. Многие заучивают книги перед собеседованиями, боятся все забыть и не могут отличить, «пардон», абстрактный от статического. Беда.
                                            +1
                                            Не сочтите за пиар, но для более полного понимания/углублённого изучения я бы посоветовал эту статью.
                                              –2
                                              Так как события в .NET реализованы криво, прогрессивное человечество использует Rx.
                                                0
                                                Решили похвастаться в статье для новичков? Аплодисменты в студию.
                                                  –1
                                                  Автор, ты какой-то мнительный.
                                                    0
                                                    Больше не буду
                                                  0
                                                  А что кривого в событиях C#? Опишите чем конкретно недовольно прогрессивное человечество
                                                    +2
                                                    А что кривого в событиях 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-методами. Не предусмотрено библиотечного механизма для разделения «холодных» и «горячих» потоков событий, буферизующих, и т.д.
                                                      +1
                                                      Внушает. После такого фундаментального комментария осталось только написать про то, как эти проблемы решены в упомянутом Rx.
                                                        0
                                                        Ещё забыл упомянуть: при возбуждении исключения в одном из обработчиков стандартных событий C#, последующие обработчики не будут выполнены. Ну нелепо же.

                                                        как эти проблемы решены в упомянутом Rx.

                                                        Они не то чтобы решены, они просто изначально не заложены в дизайн.
                                                  0
                                                  Как-то мне кажется дизайн приложения хромает. Событие onCount должно бы файриться на каждой итерации цикла со значением i в EventArgs, а уже сами подписчики должны решать, что им с этим чудом делать. :)
                                                    0
                                                    Недавно начал изучать C# после многих лет на плюсах. И вот, обнаруживаю, что в C# существует стандартный механизм сигналов, очень для меня полезный. Но вдруг выясняется, что я не могу просто привязать некий объект к событиям и заставить его отписаться от событий во время его уничтожения без каких-то дополнительных действий, воспользовавшись этим стандартным механизмом. А как же RAII? Видимо, я что-то не понимаю в концепции языка.

                                                    В каких случаях чаще всего используются события с C#? Кто контролирует отписку?
                                                      +1
                                                      > А как же 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);
                                                      0
                                                      Просто, доступно понятно. Пускай полежит у меня в избранном

                                                      Only users with full accounts can post comments. Log in, please.