В C #, почему я не могу проверить, является ли обработчик события нулевым где-либо вне класса, который он определил? - PullRequest
33 голосов
/ 07 августа 2009

Я уверен, что я просто не понимаю что-то фундаментальное о событиях и / или делегатах в C #, но почему я не могу выполнить булевы тесты в этом примере кода:

public class UseSomeEventBase {
    public delegate void SomeEventHandler(object sender, EventArgs e);
    public event SomeEventHandler SomeEvent;
    protected void OnSomeEvent(EventArgs e) {
        // CANONICAL WAY TO TEST EVENT. OF COURSE, THIS WORKS.
        if (SomeEvent != null) SomeEvent(this, e);
    }
}

public class UseSomeEvent : UseSomeEventBase {
    public bool IsSomeEventHandlerNull() {
        // "LEFT HAND SIDE" COMPILER ERROR
        return SomeEvent == null;
    }
}

class Program {
    static void Main(string[] args) {
        var useSomeEvent = new UseSomeEvent();
        useSomeEvent.SomeEvent +=new UseSomeEventBase.SomeEventHandler(FuncToHandle);
        // "LEFT HAND SIDE" COMPILER ERROR
        if (useSomeEvent.SomeEvent == null) {

        }
        var useSomeEventBase = new UseSomeEventBase();
        useSomeEventBase.SomeEvent += new UseSomeEventBase.SomeEventHandler(FuncToHandle);
        // "LEFT HAND SIDE" COMPILER ERROR
        if (useSomeEventBase.SomeEvent == null) {

        }
    }

    static void FuncToHandle(object sender, EventArgs e) { }
}

Ответы [ 7 ]

58 голосов
/ 07 августа 2009

Событие - это просто операция добавления и удаления. Вы не можете получить значение, вы не можете установить значение, вы не можете вызвать его - вы можете просто подписать обработчик на событие (add) или отменить подписку (remove). Это хорошо - это инкапсуляция, простая и понятная. Издатель должен соответствующим образом реализовать добавление / удаление, но если издатель не решит сделать детали доступными, подписчики не смогут изменить или получить доступ к частям, связанным с реализацией.

Полевые события в C # (где вы не указываете биты добавления / удаления) скрывают это - они создают переменную типа делегата и событие. Реализации добавления / удаления события просто используют переменную для отслеживания подписчиков.

Внутри класса вы ссылаетесь на переменную (чтобы вы могли получить подписанных в данный момент делегатов, выполняете их и т. Д.), А вне класса вы ссылаетесь на само событие (поэтому имеете только возможности добавления / удаления).

Альтернатива полевым событиям - это то, где вы явно реализуете добавление / удаление самостоятельно, например

private EventHandler clickHandler; // Normal private field

public event EventHandler Click
{
    add
    {
        Console.WriteLine("New subscriber");
        clickHandler += value;
    }
    remove
    {
        Console.WriteLine("Lost a subscriber");
        clickHandler -= value;
    }
}

См. мою статью о событиях для получения дополнительной информации.

Конечно, издатель событий может также сделать доступной дополнительную информацию - вы можете написать свойство, например ClickHandlers, для возврата текущего многоадресного делегата, или HasClickHandlers для возврата, есть ли или не. Это не является частью модели основного события.

15 голосов
/ 07 декабря 2010

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

Можно использовать любой из 2 подходов ниже:

  1. Подход с флагом : _getWarehouseForVendorCompletedSubscribeed является частной переменной, инициализированной как false.

        if (!_getWarehouseForVendorCompletedSubscribed)
        {
            _serviceClient.GetWarehouseForVendorCompleted += new EventHandler<GetWarehouseForVendorCompletedEventArgs>(_serviceClient_GetWarehouseForVendorCompleted);
            _getWarehouseForVendorCompletedSubscribed = true;
        }
    
  2. Подход отказа от подписки : включайте отмену подписки каждый раз, когда вы хотите подписаться.

    _serviceClient.GetWarehouseForVendorCompleted -= new 
        EventHandler<GetWarehouseForVendorCompletedEventArgs>  
     (_serviceClient_GetWarehouseForVendorCompleted);
    
    
    _serviceClient.GetWarehouseForVendorCompleted += new 
           EventHandler<GetWarehouseForVendorCompletedEventArgs>
           (_serviceClient_GetWarehouseForVendorCompleted);
    
3 голосов
/ 09 марта 2012

Вот ответ:

using System;
delegate void MyEventHandler();
class MyEvent
{
    string s;
    public event MyEventHandler SomeEvent;
    // This is called to raise the event.
    public void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            SomeEvent();
        }

    }

    public string IsNull
    {
        get
        {
            if (SomeEvent != null)
                 return s = "The EventHandlerList is not NULL";
            else return s = "The EventHandlerList is NULL"; ;
        }
    }
}

class EventDemo
{  
    // An event handler.
    static void Handler()
    {
       Console.WriteLine("Event occurred");
    }

    static void Main()
    {
       MyEvent evt = new MyEvent();
       // Add Handler() to the event list.
       evt.SomeEvent += Handler;
       // Raise the event.
       //evt.OnSomeEvent();
       evt.SomeEvent -= Handler;
       Console.WriteLine(evt.IsNull);
       Console.ReadKey();
   }
} 
2 голосов
/ 07 августа 2009

Вот немного другой вопрос

Какое значение имеет тестирование внешне определенного события на null?

Как внешний пользователь события, вы можете сделать только 2 операции

  • Добавить обработчик
  • Удалить обработчик

Нулевое или ненулевое значение события не имеет отношения к этим двум действиям. Почему вы хотите запустить тест, который не дает ощутимого значения?

1 голос
/ 07 августа 2009

Это правило действует при использовании ключевого слова 'event'. Когда вы создаете событие, вы ограничиваете взаимодействие внешнего класса с делегатом отношением «подписаться / отписаться», включая случаи наследования. Помните, что событие, по сути, является свойством, но для вызовов методов оно не является на самом деле самим объектом, поэтому на самом деле оно выглядит примерно так:

public event SomeEventHandler SomeEvent
{
     add
     {
          //Add method call to delegate
     }
     remove
     {
          //Remove method call to delegate
     }
}
0 голосов
/ 08 августа 2014

Издатель события неявно перегружает только операции += и -=, а другие операции не выполняются в издателе из-за очевидных причин, как объяснено выше, например, из-за того, что не хотят давать контроль подписчику на изменение события.

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

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

0 голосов
/ 07 августа 2009

Вы должны сделать это из базового класса. Именно поэтому вы сделали это:

protected void OnSomeEvent(EventArgs e) {
    // CANONICAL WAY TO TEST EVENT. OF COURSE, THIS WORKS.
    if (SomeEvent != null) SomeEvent(this, e);
}

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

...