Почему C # требует от вас писать нулевую проверку каждый раз, когда вы запускаете событие? - PullRequest
21 голосов
/ 23 июня 2010

Мне это кажется странным - VB.NET неявно обрабатывает нулевую проверку через ключевое слово RaiseEvent.Похоже, что это значительно увеличивает количество шаблонов вокруг событий, и я не вижу, какую пользу это дает.

Я уверен, что у разработчиков языков была хорошая причина сделать это ... но мне любопытно, еслиКто-нибудь знает почему.

Ответы [ 9 ]

18 голосов
/ 23 июня 2010

Это, безусловно, раздражает.

Когда вы пишете код, который обращается к полевому событию внутри класса, вы фактически получаете доступ к самому полю (по модулю нескольких измененийв C # 4, давайте пока не будем здесь).

Итак, параметры будут такими:

  • Полевые вызовы событий в особом случае, чтобы они на самом деле не ссылалисьнепосредственно к полю, но вместо этого добавил оболочку
  • Обрабатывать все делегировать вызовы по-разному, так что:

    Action<string> x = null;
    x();
    

    не будет вызывать исключение.

Конечно, для недействительных делегатов (и событий) оба варианта создают проблему:

Func<int> x = null;
int y = x();

Должно ли это молча возвращать 0?(Значение по умолчанию int.) Или это действительно маскировка ошибки (более вероятно).Было бы несколько непоследовательным заставить его молча игнорировать тот факт, что вы пытаетесь вызвать нулевого делегата.В этом случае было бы еще более странно, если бы не использовался синтаксический сахар C #:

Func<int> x = null;
int y = x.Invoke();

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

13 голосов
/ 23 июня 2010

мы обычно работаем над этим, объявляя наши события следующим образом:

public event EventHandler<FooEventArgs> Foo = delegate { };

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

// old, busted code that happens to work most of the time since
// a lot of code never unsubscribes from events
protected virtual void OnFoo(FooEventArgs e)
{
    // two threads, first checks for null and succeeds and enters
    if (Foo != null) {
        // second thread removes event handler now, leaving Foo = null
        // first thread resumes and crashes.
        Foo(this, e);
    }
}

// proper thread-safe code
protected virtual void OnFoo(FooEventArgs e)
{
     EventHandler<FooEventArgs> handler = Foo;
     if (handler != null)
         handler(this, e);
}

Но с автоматической инициализацией Foo пустым делегатом проверка никогда не требуется, и код автоматически поточно-ориентирован, и его легче читать при загрузке:

protected virtual void OnFoo(FooEventArgs e)
{
    Foo(this, e); // always good
}

С извинениями перед Пэт Морита в «Карате Кид»: «Лучший способ избежать нуля - это не один».

Что касается того, почему C # не балует вас так сильно, как VB. Хотя ключевое слово события скрывает большинство деталей реализации многоадресных делегатов , оно обеспечивает более точное управление, чем VB.

8 голосов
/ 23 июня 2010

Вам нужно подумать, какой код потребуется, если установка сантехники для инициирования события в первую очередь будет дорогой (например, SystemEvents) или когда подготовка аргументов события будет дорогой (например, событие Paint).

Стиль обработки событий в Visual Basic не позволяет откладывать расходы на поддержку такого события. Вы не можете переопределить добавление / удаление аксессуаров, чтобы отложить установку дорогостоящей сантехники. И вы не можете обнаружить, что не может быть подписчиков обработчиков событий, так что запись циклов для подготовки аргументов событий - пустая трата времени.

Не проблема в C #. Классический компромисс между удобством и контролем.

5 голосов
/ 28 июня 2010

Методы расширения обеспечивают очень крутой способ обойти это.Рассмотрим следующий код:

static public class Extensions
{
    public static void Raise(this EventHandler handler, object sender)
    {
        Raise(handler, sender, EventArgs.Empty);
    }

    public static void Raise(this EventHandler handler, object sender, EventArgs args)
    {
        if (handler != null) handler(sender, args);
    }

    public static void Raise<T>(this EventHandler<T> handler, object sender, T args)
        where T : EventArgs
    {
        if (handler != null) handler(sender, args);
    }
}

Теперь вы можете просто сделать это:

class Test
{
    public event EventHandler SomeEvent;

    public void DoSomething()
    {
        SomeEvent.Raise(this);
    }
}

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

3 голосов
/ 23 июня 2010

Потому что RaiseEvent несет некоторые накладные расходы.

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

  • VB.Net: простота использования,
  • C #: больше контроля как VB
  • C ++: еще больше контроля, меньше наведения, легче выстрелить себе в ногу
3 голосов
/ 23 июня 2010

Не знаю, зачем это делается, но есть вариант Шаблон нулевого объекта специально для делегатов:

private event EventHandler Foo = (sender, args) => {};

Таким образом, вы можете свободно вызывать Foo, даже не проверяя null.

1 голос
/ 16 мая 2017

Обратите внимание, что начиная с C # 6, язык теперь предоставляет краткий синтаксис для удобного выполнения этой нулевой проверки. E.g.:

public event EventHandler SomeEvent;

private void M()
{
    // raise the event:
    SomeEvent?.Invoke(this, EventArgs.Empty);
}

См. Нулевой условный оператор

1 голос
/ 03 июля 2014

Редактировать: Как указывает ОП, этот ответ не затрагивает основную часть вопроса.Тем не менее, некоторые могут найти его полезным, потому что он дает ответ на заголовок вопроса (если он взят сам по себе):

Почему C # требует, чтобы вы записывали нулевую проверку каждый раз, когда вы запускаетеevent?

Он также предоставляет контекст для намерения основной части вопроса, который некоторые могут найти полезным.Итак, по этим причинам и этот совет по Meta я оставлю этот ответ в силе.


Оригинальный текст:

Inстатья MSDN Как: публиковать события, соответствующие рекомендациям .NET Framework (Руководство по программированию в C #) (Visual Studio 2013) , Microsoft включает в свой пример следующий комментарий:

// Make a temporary copy of the event to avoid possibility of 
// a race condition if the last subscriber unsubscribes 
// immediately after the null check and before the event is raised.

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

// Wrap event invocations inside a protected virtual method 
// to allow derived classes to override the event invocation behavior 
protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
{
    // Make a temporary copy of the event to avoid possibility of 
    // a race condition if the last subscriber unsubscribes 
    // immediately after the null check and before the event is raised.
    EventHandler<CustomEventArgs> handler = RaiseCustomEvent;

    // Event will be null if there are no subscribers 
    if (handler != null)
    {
        // Format the string to send inside the CustomEventArgs parameter
        e.Message += String.Format(" at {0}", DateTime.Now.ToString());

        // Use the () operator to raise the event.
        handler(this, e);
    }
}
0 голосов
/ 23 июня 2010

Причина действительно сводится к тому, что C # дает вам больше контроля. В C # вам не нужно проверять null, если вы того пожелаете. В следующем коде MyEvent никогда не может быть null, так зачем беспокоиться о проверке?

public class EventTest
{
  private event EventHandler MyEvent = delegate { Console.WriteLine("Hello World"); }

  public void Test()
  {
    MyEvent(this, new EventArgs());
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...