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

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

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

21
задан Sarah Vessels 25 June 2010 в 21:41
поделиться

7 ответов

Это, конечно, вызывает раздражение.

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

Итак, возможны следующие варианты:

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

    Action x = null;
    x();
    

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

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

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

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

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

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

18
ответ дан 29 November 2019 в 06:56
поделиться

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

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

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

3
ответ дан 29 November 2019 в 06:56
поделиться

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

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

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

8
ответ дан 29 November 2019 в 06:56
поделиться

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

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

  public void Test()
  {
    MyEvent(this, new EventArgs());
  }
}
0
ответ дан 29 November 2019 в 06:56
поделиться

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

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

  • VB.Net: простота использования,
  • C#: больше контроля, как в VB
  • C++: еще больше контроля, меньше руководства, легче выстрелить себе в ногу
3
ответ дан 29 November 2019 в 06:56
поделиться

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

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);
    }
}

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

5
ответ дан 29 November 2019 в 06:56
поделиться

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

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

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

// 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
}

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

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

13
ответ дан 29 November 2019 в 06:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: