Подтвердить что ты не робот

События не являются полями - я не понимаю

В С# in depth (отличная книга до сих пор), Skeet объясняет, что события не являются полями. Я читал этот раздел много раз, и я не понимаю, почему различие имеет какое-то значение.

Я один из тех разработчиков, которые путают события и делегируют экземпляры. На мой взгляд, они одинаковы. Разве это не просто форма косвенности? Мы можем многоадресной рассылки. Событие настраивается как поле как сокращенное... конечно. Но мы добавляем или удаляем обработчики. Укладка их для вызова при срабатывании события. Разве мы не делаем то же самое с делегатами, не складываем их и не вызываем вызов?

4b9b3361

Ответ 1

Другие ответы в основном правильные, но вот еще один способ взглянуть на это:

Я один из тех разработчиков, которые путают события и делегируют экземпляры. На мой взгляд, они одинаковы.

На ум приходит старая поговорка о том, чтобы не увидеть лес для деревьев. Различие, которое я делаю, состоит в том, что события находятся на более высоком "семантическом уровне", чем поле экземпляра делегата. Событие говорит потребителю типа "привет там, я тип, который любит рассказывать вам, когда что-то происходит". Тип источника события; это часть его публичного контракта.

Как в качестве детали реализации этот класс выбирает отслеживать, кто заинтересован в прослушивании этого события, и что и когда сообщать подписчикам о том, что событие происходит, является бизнесом этого класса. Обычно это происходит с делегатом многоадресной рассылки, но это детализация реализации. Это такая общая деталь реализации, что разумно смешивать два, но у нас действительно есть две разные вещи: открытая поверхность и частная реализация.

Аналогично, свойства описывают семантику объекта: у клиента есть имя, поэтому класс Customer имеет свойство Name. Вы можете сказать, что "их имя" является собственностью клиента, но вы никогда не скажете, что "их имя" является полем клиента; что деталь реализации конкретного класса, а не факт о бизнес-семантике. То, что свойство обычно реализуется как поле, является частной деталью механики класса.

Ответ 2

Свойства также не являются полями, хотя они и выглядят как они. На самом деле это пара методов (getter и setter) со специальным синтаксисом.

События аналогично представляют собой пару методов (подписываются и отписываются) со специальным синтаксисом.

В обоих случаях у вас обычно есть частное "поле поддержки" внутри вашего класса, которое содержит значение, обрабатываемое методами getter/setter/subscribe/unsubscribe. И есть автоматически реализованный синтаксис для обоих свойств и событий, когда компилятор создает для вас поле поддержки и методы доступа.

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

Ответ 3

Рассмотрим два способа объявления событий.

Либо вы объявляете событие, используя явный метод add/remove, либо объявляете событие без таких методов.

Другими словами, вы объявляете событие следующим образом:

public event EventHandlerType EventName
{
    add
    {
        // some code here
    }
    remove
    {
        // some code here
    }
}

или объявите его следующим образом:

public event EventHandlerType EventName;

Дело в том, что в некотором смысле это одно и то же, и в других отношениях они совершенно разные.

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

Разница в том, что во втором примере выше, эти методы будут предоставлены компилятором для вас, однако, что все равно, как это будет. Чтобы подписаться на событие, вы вызываете метод.

Синтаксис для этого, в С#, однако, тот же, вы также:

objectInstance.EventName += ...;

или

objectInstance.EventName -= ...;

Итак, из "внешней точки зрения" оба способа вообще не отличаются.

Однако внутри класса существует разница.

Если вы попытаетесь получить доступ к идентификатору EventName внутри класса, вы на самом деле ссылаетесь на field, который поддерживает свойство , но только если вы используете синтаксис, который явно не объявляет add/remove.

Типичный шаблон выглядит следующим образом:

public event EventHandlerType EventName;

protected void OnEventName()
{
    var evt = EventName;
    if (evt != null)
        evt(this, EventArgs.Empty);
}

В этом случае, когда вы ссылаетесь на EventName, вы действительно ссылаетесь на поле, которое содержит делегат типа EventHandlerType.

Однако, если вы явно объявили методы add/remove, ссылаясь на идентификатор EventName внутри класса, будет как вне класса, так как компилятор не может гарантировать, что он знает поле, или любой другой механизм, в котором вы сохраняете подписку.

Ответ 4

Событие является аксессуаром для делегата. Точно так же, как свойство является аксессуаром для поля. С помощью той же самой утилиты он не позволяет коду взаимодействовать с объектом делегата. Как свойство имеет get и set accessor, событие имеет возможность добавления и удаления доступа.

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

Вы не часто это делаете, но это, безусловно, не является чем-то необычным. Рамка .NET довольно часто делает это, например, события элементов управления Winforms хранятся в EventHandlerList и манипуляторы добавления/удаления которые перечислены через его методы AddHandler() и RemoveHandler(). С тем преимуществом, что для всех событий (их много) требуется только одно поле в классе.

Ответ 5

Я могу добавить к прежним ответам, что делегаты могут быть объявлены внутри области пространства имен (вне класса), а события могут быть объявлены только внутри класса. Это потому, что делегат - это класс!

Другое отличие состоит в том, что для событий единственный класс, который может его запустить, - это класс. Вы можете подписаться/отказаться от подписки на него через класс, но не может его запустить (в отличие от делегатов). Поэтому, возможно, теперь вы можете понять, почему соглашение состоит в том, чтобы обернуть его внутри protected virtual OnSomething(object sender, EventArgs e). Это означает, что потомки смогут переопределить выполнение стрельбы.