An event can contain many handlers which are defined using delegate
, my current understanding is that delegate is just an abstraction of function pointer. Since an event
, which associated with a delegate
type, can add / remove many delegates into it, and composite pattern treats a composite object the same as the terminal object, so the idea is:
composite.onTriggered();
// Internally:
// foreach(handler in composite)
// {
// handler.onTriggered();
// }
will in turn call every handlers managed by the composite
.
But it seems like public event EventHandler ThresholdReached
does not define a composite, see my comment in the code below
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
}
Am I correct about the idea in abstract level? If not could you provide any correction(s)?
Delegate is a type that defines a signature and holds a reference of method whose signature matches with the delegate. Event is a notification raised by an object to signal the occurrence of an action. Delegate is associated with the event to hold a reference of a method to be called when the event is raised.
An event is declared using the event keyword. Delegate is a function pointer. It holds the reference of one or more methods at runtime. Delegate is independent and not dependent on events.
Multicast delegates help to invoke multiple callbacks. Events encapsulate delegate and implement publisher and subscriber model.
Yes, Events! This is a simplified explanation on why we use events even though we can already use delegates: To provide encapsulation and not exposing business logic. To prevent Team Client from clearing all assign methods to delegates (You cannot do that for events):
Event is a notification raised by an object to signal the occurrence of an action. Delegate is associated with the event to hold a reference of a method to be called when the event is raised. In the next article we will look at the Observer and Pub-Sub pattern which is based on the Event-Delegate Model.
The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly.
Events and Delegates in C# are undoubtedly among the most confusing topics. Let us explore more about these. The code used in this article can be downloaded from GitHub. Delegates are function pointers. Let’s write some code using delegates to understand delegates.
Composite or Compound Events Events which can be further sub-divided into (or viewed in the form of) smaller events are called composite events or compound events. Events which cannot be further sub - divided into smaller events are called elementary or simple events.
Directly answering your question, I would say: No, there is no relation between events and delegates adopting the composite pattern. Delegates design yes, it follows the composite pattern. Events not. (Moreover, notice that you do not need events to take advantage of delegates. (see DelegateBased
bellow)) (I will answer your comment about "So what's the point of this line?" at the end as a side note)
Nevertheless, the Delegate type itself follows the composite approach in terms that “The composite pattern describes a group of objects that are treated the same way as a single instance of the same type of object.”.
In turn, as @Flydog57 and @mark-seemann have already mentioned the .NET event model follows the observer pattern.
The relation between Events and Delegates regards the Event declaration that may require a Delegate type (the TypeSpec
), as it is stated in the section II.18 Defining events of ECMA-335 (CLI) Partitions I to VI (the standard):
In typical usage, the TypeSpec (if present) identifies a delegate whose signature matches the arguments passed to the event’s fire method.
To make it clear, check the following two equivalent examples where EventBased
uses events without a delegate field and DelegateBased
uses a delegate field without events. Notice that I say distinctly delegate field or delegate type. They are not the same. Both examples need a delegate type which is declared as the following for this example:
delegate void Observer();
You may run both examples with:
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
Next, the two implementations of EventBased
and DelegateBased
that use names according to the example of 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
}
}
Regarding your comment about:
EventHandler handler = ThresholdReached; // So what's the point of this line?
handler?.Invoke(this, e);
The reason it is clearly identified by Jeffrey Richter in its masterpiece "Clr via C#" in Chapter 11 - Events at "Raising an Event in a Thread-Safe Way" (see the NewMail
as the ThresholdReached
of your example) where it states:
The problem with the
OnNewMail
method is that the thread could see thatNewMail
is not null, and then, just before invokingNewMail
, another thread could remove a delegate from the chain makingNewMail
null
, resulting in aNullReferenceException
being thrown.
As Flydog57 points out, the .NET event model is essentially the Observer pattern built into the language, just like IEnumerable
and foreach
implements the Iterator pattern.
The patterns in the Gang of Four book are, however, at various levels of abstraction. I'm not sure that this was clear to anyone in 1994, but with decades of use it's becoming increasingly clear (to me, at least) that some of those patterns are more general than others. One such pattern is the Adapter pattern, where you can view the Decorator pattern as a degenerate specialisation.
Another such pattern is Composite. You can view some of the other patterns in the books as specialisations of Composite. Not only Observer, but also Command and State (at least as described in the book); possibly others.
I think that your intuition is correct. While events are most specifically patterned after the Observer pattern, you can also think of it as a Composite. This may be clearer if we consider Reactive Extensions (Rx) and the IObserver<T>
interface instead of .NET events. IIRC, Rx defines conversions between .NET events and its own model.
More generally, every API that gives rise to a monoid can be modelled as a Composite. Since events return no data (they have void
method signatures) they form a monoid. Therefore, you can also view them as an instance of the Composite design pattern.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With