Вопрос по c#, events, .net – Каков наилучший способ реализации связанных событий в C #
У меня есть следующий сценарий. Код клиента имеет доступ только к 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. Является ли это «цепочкой событий»? что нужно сделать? Есть ли альтернативы? Мне это не нравится (кажется неправильным, я не могу точно объяснить, почему), но если я хочу, чтобы обработчик был точкой доступа, у меня, похоже, нет многих альтернатив.
Напишите объявление события следующим образом:
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;
Действительно, Решарпер может даже предложить вам это! Но это означает что-то совершенно другое. Добавляетcurrent contents изa
вb
так что если позже, то больше обработчиков подключатся сa
, это не приведет к их вызову, когдаb
уволен.
потому что события более сложные и внешние, чем это необходимо для ваших внутренних коммуникаций (что, я считаю, по крайней мере частично верно, учитывая, что события могут вызывать несколько клиентов, тогда как вы знаете, что вам нужно только уведомить одного, верно?), Тогда я предложить следующую альтернативу. Вместо использования событий для передачи завершения 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 является закрытым, или использовать механизм, подобный предложенному Павлом. Я думаю, однако, что клиентам нравится простота реализации меньшего числа обработчиков событий и различения источника внутри одного обработчика, а не необходимость подключать (и потенциально отключать) события от десятков меньших объектов.
где проходят все события. Любые классы, использующие домен, будут подключаться к этому классу, который имеет список статических событий, и любое внутреннее событие класса в домене будет прослушиваться этим классом, тем самым избегая цепочки событий в домене, по крайней мере.
Рекомендации:
Udi Dahan with code examples on domain events Martin Fowler more general on domain eventsadd
а также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
, Если он изменчив, то вам также придется отслеживать добавление / удаление элементов в нем и соответственно добавлять / удалять обработчики.
event waterfall это то, к чему я довольно естественно пришел несколько раз, и мне еще предстоит столкнуться с серьезной проблемой с ними.
Хотя я и не думаю, что когда-либо передавал события прозрачно, но всегда с семантическим изменением.FooLoaded
станетAllFoosLoaded
, например. Если вы хотите навязать такое семантическое изменение просто ради него, вы можете изменитьOneFooLoaded
к показателю в процентах (должен ли получающий класс знать, сколькоFoo
s загружены?), например.
Я думаю, что такие конструкции кажутся неправильными, потому чтоevent
предназначен для трансляции. Он на самом деле не навязывает контракт классу, который его передает, и не налагает контракт на класс, подписывающийся на него.
Тем не менее, классы Fa &dea и общие принципы сокрытия информации призваны облегчить выполнение контрактов.
Я все еще собираю свои мысли по этому вопросу, извините, если вышеизложенное немного неясно, но я не знаю, есть ли лучший способ сделать то, что вы хотите. И если это так, мне так же интересно видеть его, как и вы.