C # :: Когда использовать события или коллекцию объектов, полученных из интерфейса обработки событий? - PullRequest
5 голосов
/ 02 июня 2009

У меня есть, как мне кажется, простая «проблема», для которой я нашел пару решений, но я не уверен, какой путь и лучший метод в C #.

У меня есть главный объект (скажем, одноэлементный), созданный один раз за время существования приложения. Этот «MasterClass» создает группу объектов нового типа, скажем «SlaveClass», каждый раз, когда вызывается MasterClass.Instance.CreateSlaveObject.

Этот MasterClass также отслеживает некоторые другие объекты на предмет изменения статуса, и когда это происходит, уведомляет созданные им объекты SlaveClass об изменении. Кажется достаточно простым.

Так как я из родного мира C ++, то, как я это сделал сначала, чтобы иметь интерфейс

Interface IChangeEventListener
{
    void ChangeHappened();
}

из которого я получил "SlaveClass". Тогда в моем "MasterClass" у меня есть:

...
IList<IChangeEventListener> slaveList;
...
CreateSlaveObject
{
    ...
    slaveList.Add(slave);
}
...
ChangeHappened()
{
    ...
    foreach(var slave in slaveList)
    {
       slave.ChangeHappened();
    }
}

И это работает. Но я все время думал о том, есть ли другой (лучший) способ сделать это. Поэтому я немного больше изучил эту тему и увидел события C #.

Таким образом, вместо того, чтобы поддерживать коллекцию ведомых в MasterClass, я бы в основном внедрил MasterClass в ctor SlaveClass (или через свойство) и позволил бы объекту SlaveClass добавить свой ChangeHappened в качестве обработчика событий. это будет проиллюстрировано:

  ...Master...          
  public delegate void ChangeHappenedDelegate(object sender, NewsInfoArgs args);
  public event NewUpdateDelegate ChangeHappenedEvent;
  ....

  public SlaveClass (MasterClass publisher) //inject publisher service
  {
      publisher.ChangeHappenedEvent += ChangeHappened;
  }

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

Так должен ли я сохранить свой текущий код или перейти к подходу, основанному на событиях (с внедрением издателя)? а почему?

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

Ответы [ 4 ]

7 голосов
/ 02 июня 2009

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

Я думаю о событиях так: «Мне нужно подписаться на ваше событие, потому что мне нужно, чтобы вы сообщали мне, когда с вами что-то случится».

Принимая во внимание, что интерфейсом является «Мне нужно вызвать метод для вас, чтобы сообщить вам, что со мной что-то случилось».

Это может звучать так же, но это отличается от того, кто говорит, в обоих случаях говорит ваш "мастер-класс", и в этом вся разница.

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

SlaveClass sc = new SlaveClass();
ChangeHappenedEvent += sc.ChangeHappened;
return sc;

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

Объекты SlaveClass живут так же долго, как класс singleton? Если нет, то вам нужно обработать случай, когда они устареют / больше не нужны, как в приведенном выше случае (в основном как у вас, так и у меня), вы держите ссылку на эти объекты в вашем MasterClass, и, таким образом, они никогда не будет иметь право на сборку мусора, если вы не удалите эти события принудительно или не зарегистрируете интерфейсы.


Чтобы решить проблему с SlaveClass, который не живет так долго, как MasterClass, вы столкнетесь с той же проблемой связи, как вы также отметили в комментарии.

Один из способов «обработки» (обратите внимание на кавычки) может заключаться в том, чтобы на самом деле не ссылаться непосредственно на правильный метод объекта SlaveClass, а вместо этого создать объект-обертку, который будет внутренне вызывать этот метод. Преимущество этого состоит в том, что объект-оболочка может использовать объект WeakReference для внутреннего использования, так что, как только ваш объект SlaveClass пригоден для сборки мусора, он может быть собран, а затем, когда вы в следующий раз попытаетесь вызвать для него правильный метод, заметит это, и поэтому вам придется вычистить.

Например, вот так (и здесь я печатаю без использования IntelliSense Visual Studio и компилятора, пожалуйста, примите значение этого кода, а не синтаксис (ошибки).)

public class WrapperClass
{
    private WeakReference _Slave;

    public WrapperClass(SlaveClass slave)
    {
        _Slave = new WeakReference(slave);
    }

    public WrapperClass.ChangeHappened()
    {
        Object o = _Slave.Target;
        if (o != null)
            ((SlaveClass)o).ChangeHappened();
        else
            MasterClass.ChangeHappenedEvent -= ChangeHappened;
    }
}

В вашем MasterClass вы бы сделали что-то вроде этого:

SlaveClass sc = new SlaveClass();
WrapperClass wc = new WrapperClass(sc);
ChangeHappenedEvent += wc.ChangeHappened;
return sc;

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

0 голосов
/ 27 августа 2011

Несмотря на то, что события были бы нормальной парадигмой для раскрытия общедоступных функций подписки / отписки, во многих случаях они не являются лучшей парадигмой, когда функциональность подписки / отмены подписки не нуждается в представлении. В этом сценарии единственное, что разрешено подписать / отписаться от события, это то, что вы сами создаете, поэтому нет необходимости в публичных методах подписки / отписки. Кроме того, знания мастера о рабах намного превосходят знания подписчиков типового события. Поэтому я предпочитаю, чтобы мастер явно обрабатывал подключение / отключение подписок.

Одна особенность, которую я, вероятно, добавил бы, хотя, которая несколько упрощается благодаря связи между ведущим и ведомым, могла бы позволить сборщикам мусора собираться, если все внешние ссылки будут оставлены. Самый простой способ сделать это, вероятно, состоит в том, чтобы мастер вел список «слабых ссылок» для рабов. Когда необходимо уведомить рабов о том, что что-то произошло, просмотрите список, разыменуйте любую WeakReference, которая еще жива, и уведомите раб. Если после последней очистки было добавлено более половины элементов в списке, и список содержит не менее 250 или около того элементов, скопируйте все действующие ссылки в новый список и замените старый.

Альтернативный подход, который позволил бы избежать необходимости разыменования объектов WeakReference, состоял бы в том, чтобы публичный «ведомый» объект был оберткой для частного объекта, а публичный объект переопределял Finalize, чтобы частный объект знал, что нет внешние ссылки на него существуют. Это потребует добавления дополнительного уровня сильной косвенности для публичного доступа к объекту, но это позволит избежать создания - даже на мгновение - сильных ссылок на заброшенные объекты.

0 голосов
/ 02 июня 2009

Поскольку у вас, кажется, есть одноэлементный экземпляр MasterClass, почему бы не подписаться на MasterClass.Instance.ChangeHappenedEvent? Это все еще крепкая муфта, но относительно аккуратная.

0 голосов
/ 02 июня 2009

Я думаю, что это просто вопрос личных предпочтений ... лично мне нравится использовать события, потому что они лучше соответствуют "философии" .NET.

В вашем случае, если MasterClass является синглтоном, вам не нужно передавать его конструктору SlaveClass, поскольку его можно получить с помощью свойства (или метода) singleton:

public SlaveClass ()
{
    MasterClass.Instance.ChangeHappenedEvent += ChangeHappened;
}
...