Каков наилучший способ реализации связанных событий в C # - PullRequest
6 голосов
/ 12 июля 2009

У меня есть следующий сценарий. Код клиента имеет доступ только к FooHandler, но не напрямую к экземплярам Foo.

public delegate void FooLoaded(object sender, EventArgs e);

class Foo {
    public event FooLoaded loaded;
    /* ... some code ... */
    public void Load() { load_asynchronously(); }
    public void callMeWhenLoadingIsDone() { loaded(this,EventArgs.Empty); }
}

class FooHandler {
    public event FooLoaded OneFooLoaded;

    /* ... some code ... */

    public void LoadAllFoos() { 
        foreach (Foo f in FooList) { 
            f.loaded += new FooLoaded(foo_loaded);
            f.Load(); 
        } 
    }

    void foo_loaded(object sender, EventArgs e) {
        OneFooLoaded(this, e);
    }

}

Тогда клиенты будут использовать событие OneFooLoaded класса FooHandler для получения уведомлений о загрузке foos. Это «цепочка событий» - правильная вещь? Есть ли альтернативы? Мне это не нравится (кажется неправильным, я не могу точно объяснить, почему), но если я хочу, чтобы обработчик был точкой доступа, у меня, похоже, нет многих альтернатив.

Ответы [ 5 ]

4 голосов
/ 12 июля 2009

Если это кажется неправильным, потому что события являются более сложными и внешними, чем это необходимо для вашей внутренней коммуникации (что, я считаю, по крайней мере частично верно, учитывая, что события могут вызывать несколько клиентов, тогда как вы знаете, что вам нужно только уведомить одного, верно?) Тогда я предлагаю следующую альтернативу. Вместо использования событий для передачи завершения Foo в FooHandler, поскольку Foo в любом случае является внутренним, вы можете добавить параметр обратного вызова в конструктор или метод Load Foo, который Foo может вызывать после завершения загрузки. Этот параметр может быть просто функцией, если у вас есть только один обратный вызов, или это может быть интерфейс, если у вас их много. Вот как я думаю, ваш код будет выглядеть с упрощенным внутренним интерфейсом:

public delegate void FooLoaded(FooHandler sender, EventArgs e);

class Foo
{
  Action<Foo> callback;
  /* ... some code ... */
  public void Load(Action<Foo> callback) { this.callback = callback; load_asynchronously(); }
  public void callMeWhenLoadingIsDone() { callback(this); }
}

class FooHandler
{
  public event FooLoaded OneFooLoaded;

  /* ... some code ... */

  public void LoadAllFoos()
  {
     foreach (Foo f in FooList)
     {
        f.Load(foo_loaded);
     }
  }

  void foo_loaded(Foo foo)
  {
     // Create EventArgs based on values from foo if necessary
     OneFooLoaded(this, null);
  }

}

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

Если, с другой стороны, он чувствует себя неправильно, потому что событие не должно проходить через FooHandler, чтобы добраться до клиента, то 1) Я бы оспорил это, потому что, если клиент не хочет иметь дело с человеком Объекты Foo, они также не должны отнимать события от них на этом уровне, и 2) Если вы действительно хотите это сделать, вы можете реализовать некоторый публичный интерфейс обратного вызова на Foo, даже если Foo является закрытым, или использовать механизм, подобный предложенному Павлом. , Я думаю, однако, что клиентам нравится простота реализации меньшего числа обработчиков событий и различения источника внутри одного обработчика, а не необходимость подключать (и потенциально отключать) события от десятков меньших объектов.

3 голосов
/ 12 июля 2009

Другой способ сделать это - создать единую точку (один класс) в домене, через который проходят все события. Любые классы, использующие домен, будут подключаться к этому классу, который имеет список статических событий, и любое внутреннее событие класса в домене будет прослушиваться этим классом, тем самым избегая цепочки событий в домене, по крайней мере.

Ссылки:

2 голосов
/ 12 июля 2009

Несколько советов, которые могут или не могут быть полезны ...

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

public event FooLoaded loaded = delegate {};

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

На тему цепочки событий, когда у вас есть два события:

public event EventHandler a = delegate {};
public event EventHandler b = delegate {};

Вы можете захотеть, чтобы запуск b также вызывал запуск a:

b += (s, e) => a(s, e);

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

b += a;

Действительно, Решарпер может даже предложить вам это! Но это означает что-то совершенно другое. Он добавляет текущее содержимое из a к b, так что, если позднее большее число обработчиков подключится к a, это не приведет к их вызову при срабатывании b.

1 голос
/ 12 июля 2009

Вы можете делегировать add и remove на событие вместо повышения:

class FooHandler {
    public event FooLoaded OneFooLoaded {
       add { 
           foreach (Foo f in FooList) {
               f.loaded += new FooLoaded(value);
           }
       }
       remove {
           foreach (Foo f in FooList) {
               f.loaded -= new FooLoaded(value);
           }
       }
    }

    public void LoadAllFoos() { 
        foreach (Foo f in FooList) { 
            f.Load(); 
        } 
    }
}

Приведенное выше предполагает, что FooList является неизменным в течение жизни FooHandler. Если он изменчив, вам также придется отслеживать добавление / удаление элементов и соответственно добавлять / удалять обработчики.

1 голос
/ 12 июля 2009

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

Хотя я не думаю, что когда-либо передавал события прозрачно, но всегда с семантическими изменениями. Например, FooLoaded станет AllFoosLoaded. Если вы хотите навязать такое семантическое изменение просто ради него, вы можете изменить OneFooLoaded на процентный показатель (нужно ли классу-получателю знать, сколько Foo s загружено?), Например.

Я думаю, что подобные конструкции кажутся неправильными, потому что event предназначен для вещания. На самом деле он не навязывает контракт классу, который его передает, и не налагает контракт на класс, подписывающийся на него.

Фасадные классы и общие принципы сокрытия информации, однако, предназначены для облегчения обеспечения исполнения договоров.

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

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