我能说事件和委托之间的关系采用复合模式吗?

Can I say that the relation between events and delegates adopts composite pattern?

一个事件可以包含许多使用delegate定义的处理程序,我目前的理解是委托只是函数指针的抽象。由于与 delegate 类型关联的 event 可以向其中添加/删除许多委托,并且复合模式将复合对象视为与终端对象相同,因此想法是:

composite.onTriggered();
// Internally:
// foreach(handler in composite)
// {
//     handler.onTriggered();
// }

将依次调用由 composite 管理的每个处理程序。

但是好像public event EventHandler ThresholdReached没有定义复合,看我下面代码的注释

class Counter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        EventHandler handler = ThresholdReached; // So what's the point of this line?
        handler?.Invoke(this, e);
        // Why not just:
     // ThresholdReached?.Invoke(this, e);
    } 

    // provide remaining implementation for the class
}

我在抽象层面上的想法是否正确?如果不是,您能否提供任何更正?

正如 Flydog57 指出的那样,.NET 事件模型本质上是语言内置的观察者模式,就像 IEnumerableforeach 实现迭代器模式一样。

然而,四人帮书中的模式处于不同的抽象层次。我不确定 1994 年的任何人都清楚这一点,但经过数十年的使用,(至少对我而言)越来越清楚,其中一些模式比其他模式更通用。一种这样的模式是适配器模式,您可以在其中将装饰器模式视为退化的专业化。

另一个这样的模式是 Composite。您可以将书中的一些其他模式视为 Composite 的特化。不仅是 Observer,还有 Command 和 State(至少是书中描述的);可能还有其他人。

我认为你的直觉是正确的。虽然事件是在观察者模式之后最具体地模式化的,但您也可以将其视为复合模式。如果我们consider Reactive Extensions (Rx) and the IObserver<T> interface instead of .NET events,这可能会更清楚。 IIRC,Rx 定义了 .NET 事件和它自己的模型之间的转换。

更一般地说,every API that gives rise to a monoid can be modelled as a Composite。由于事件 return 没有数据(它们具有 void 方法签名),因此它们形成了一个幺半群。因此,您也可以将它们视为 Composite 设计模式的一个实例。

直接回答你的问题,我会说:不,事件和采用复合模式的代表之间没有关系委托设计是,它遵循复合模式。 事件不。 (此外,请注意 你不需要事件来利用委托。(请参阅下面的 DelegateBased))(我将回答你关于“所以这行的重点是什么?”在最后作为旁注)

然而,委托类型本身遵循复合方法,即“复合模式描述了一组对象 的处理方式与相同类型对象的 单个实例 相同。”.

反过来,正如@Flydog57 和@mark-seemann 已经提到的那样,.NET 事件模型遵循观察者模式

事件和委托之间的关系关于事件声明可能需要委托类型(TypeSpec),正如 II.18 定义事件 ECMA-335 (CLI) Partitions I to VI(标准)部分所述:

In typical usage, the TypeSpec (if present) identifies a delegate whose signature matches the arguments passed to the event’s fire method.

为了清楚起见,请检查以下两个等效示例,其中 EventBased 使用 没有委托字段的事件 DelegateBased 使用 没有事件的委托字段。请注意,我明确地说 delegate fielddelegate type。它们不一样。这两个示例都需要一个 delegate 类型,在本示例中声明如下:

delegate void Observer();

您可以 运行 这两个示例:

var subject = new DelegateBased(); // replace it with: var subject = new EventBased();
Observer foo = () => Console.Write("Foo");
Observer bar = () => Console.Write("Bar");
subject.RegisterObserver(foo); // subject.Caller += foo;
subject.RegisterObserver(bar); // subject.Caller += bar;
subject.Notify(); // prints: FooBar
Console.WriteLine();
subject.UnregisterObserver(foo); // subject.Caller -= foo;
subject.Notify(); // prints: Bar

接下来EventBasedDelegateBased的两个实现根据Observer Pattern in Wikipedia

的例子使用名称
class EventBased {
  private List<Observer> observers = new List<Observer>();
  public event Observer Caller {
    add { RegisterObserver(value); }
    remove { UnregisterObserver(value); }
  }
  public void Notify() { foreach (var caller in observers) caller(); }
    
  public void RegisterObserver(Observer val) {  observers.Add(val); }
    
  public void UnregisterObserver(Observer val) { observers.Remove(val); }
}
class DelegateBased {
  private Observer observers; // delegate field without events
    
  public void Notify() { observers(); }

  public void RegisterObserver(Observer val) { 
    observers = (Observer) Delegate.Combine(observers, val); // <=> observers += val
  }
  public void UnregisterObserver(Observer val) {
    observers = (Observer) Delegate.Remove(observers, val); // <=> observers -= val
  }
}

关于您的评论:

EventHandler handler = ThresholdReached; // So what's the point of this line?
handler?.Invoke(this, e);

Jeffrey Richter 在其杰作“Clr via C#”中第 11 章 - 事件中“以线程安全方式引发事件”中明确指出的原因(将 NewMail 视为示例中的 ThresholdReached),其中指出:

The problem with the OnNewMail method is that the thread could see that NewMail is not null, and then, just before invoking NewMail, another thread could remove a delegate from the chain making NewMail null, resulting in a NullReferenceException being thrown.