s̮̦*_*̥̳̼ 5 c# delegates design-patterns
一个事件可以包含许多使用 定义的处理程序delegate,我目前的理解是委托只是函数指针的抽象。由于event与delegate类型关联的an可以向其中添加/删除许多委托,并且复合模式将复合对象视为终端对象,因此其思想是:
composite.onTriggered();
// Internally:
// foreach(handler in composite)
// {
// handler.onTriggered();
// }
Run Code Online (Sandbox Code Playgroud)
将依次调用由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
}
Run Code Online (Sandbox Code Playgroud)
我对抽象级别的想法是否正确?如果没有,你能提供任何更正吗?
直接回答你的问题,我会说:不,采用复合模式的事件和委托之间没有关系。代表设计是的,它遵循复合模式。事件不。(此外,请注意,您不需要事件来利用代表。(见下文))(我将在最后作为旁注回答您关于“那么这一行的意义是什么? ”的评论)DelegateBased
然而,委托类型本身遵循复合方法,即 \xe2\x80\x9c 复合模式描述了一组对象,这些对象的处理方式与同一类型对象的单个实例相同。\xe2\x80\x9d。
\n反过来,正如 @Flydog57 和 @mark-seemann 已经提到的,.NET 事件模型遵循观察者模式。
\n事件和委托之间的关系涉及可能需要委托类型 (the )的事件声明,如ECMA-335 (CLI) 第 I 至 VI 部分(标准)的II.18 定义事件部分所述:TypeSpec
\n\n在典型用法中,TypeSpec(如果存在)标识一个委托,其签名与传递给 event\xe2\x80\x99s fire 方法的参数匹配。
\n
为了清楚起见,请检查以下两个等效示例,其中EventBased使用不带委托字段的事件和DelegateBased使用不带事件的委托字段。请注意,我明确表示委托字段或委托类型。他们不一样。这两个示例都需要一个委托类型,在本示例中声明如下:
delegate void Observer();\nRun Code Online (Sandbox Code Playgroud)\n您可以使用以下命令运行这两个示例:
\nvar subject = new DelegateBased(); // replace it with: var subject = new EventBased();\nObserver foo = () => Console.Write("Foo");\nObserver bar = () => Console.Write("Bar");\nsubject.RegisterObserver(foo); // subject.Caller += foo;\nsubject.RegisterObserver(bar); // subject.Caller += bar;\nsubject.Notify(); // prints: FooBar\nConsole.WriteLine();\nsubject.UnregisterObserver(foo); // subject.Caller -= foo;\nsubject.Notify(); // prints: Bar\nRun Code Online (Sandbox Code Playgroud)\n接下来,根据维基百科中观察者模式EventBased的示例,使用名称的和的两个实现DelegateBased
class EventBased {\n private List<Observer> observers = new List<Observer>();\n public event Observer Caller {\n add { RegisterObserver(value); }\n remove { UnregisterObserver(value); }\n }\n public void Notify() { foreach (var caller in observers) caller(); }\n \n public void RegisterObserver(Observer val) { observers.Add(val); }\n \n public void UnregisterObserver(Observer val) { observers.Remove(val); }\n}\nRun Code Online (Sandbox Code Playgroud)\nclass DelegateBased {\n private Observer observers; // delegate field without events\n \n public void Notify() { observers(); }\n\n public void RegisterObserver(Observer val) { \n observers = (Observer) Delegate.Combine(observers, val); // <=> observers += val\n }\n public void UnregisterObserver(Observer val) {\n observers = (Observer) Delegate.Remove(observers, val); // <=> observers -= val\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n关于您的评论:
\nEventHandler handler = ThresholdReached; // So what\'s the point of this line?\nhandler?.Invoke(this, e);\nRun Code Online (Sandbox Code Playgroud)\nJeffrey Richter 在其杰作“Clr via C#”第 11 章“以线程安全方式引发事件”中的事件中明确指出了其原因(请参阅NewMail示例ThresholdReached的 ),其中指出:
\n\n该方法的问题
\nOnNewMail在于,线程可以看到NewMail不为 null,然后,就在调用 之前NewMail,另一个线程可以从链中删除委托NewMailnull,从而导致NullReferenceException抛出异常。