Well, if you try to create artificially simplified use cases as you present in your Question, you don't have a chance to see "Extra Advantage", right?
I would prefer talking not about "advantage", but about a principal technological feature. What's so special about delegates; what is the useful effect which we cannot achieve without them?
Let's step back and consider the simplest and most fundamental aspect of delegation, because if you understand it, you can derive all advanced uses. We should come to the case where we get some profit not achievable using customary methods. (I'm not going to touch multicast feature, because it is even irrelevant.)
First of all, let's use the word "delegate" only for a delegate type, and the instances of a delegate types call "delegate instance", not mixing them. First, it is important to understand, that a delegate instance is a variable of a method parameter. That said, delegate instances are considered as "first-class citizens" objects. At the same time, a delegate instance represents some method (anonymous or not), in this way, methods can be considered as "first-class citizens" objects.
Now, this is the time to consider anatomy of a delegate instance. For our current purpose, we can consider a delegate instance as a data structure of two essential parts: a reference to the code entry point and a reference to some object of the class. The reference to object can be null, for static methods. In other words, we have a class
C
, the method
C.M
,
C.S
(let's make a second one static) and an instance of this class
cinst
:
class C {
public delegate void DeletateType();
internal void M() {};
internal static void S() {}
}
C cinst = new C();
Based on that, we can have two delegate instances:
[C.M, cinst]
and
[C.S, null]
, importantly,
cinst
is the instance of the same class as the method. (There is also a reference to delegate type meta-data, let's forget it for a while.)
As you should understand, every instance method is called having additional parameter
this
(absent in static methods) used to carry a reference to the instance. Having a data structure like in my pseudo-code
[C.M, cinst]
, we have everything to call a method: we call at the entry point
C.M
and use
this=cinst
as a first parameter, other parameters are passes explicitly, according to the delegate type.
Now, let's remember the delegate instance is a variable and can be passed as a method parameter. So, we can have
completely unrelated class using
C.DelegateType
as an additional level of abstraction:
class DelegateUser {
internal void CompositeAction(
int data,
C.DelegateType injectedAction,
injectedActionParameter) {
if (injectedAction != null)
injectedAction(injectedActionParameter);
}
}
DelegateUser user = new DelegateUser(
user.C CompositeAction(3, new C().M(4, );
This can be considered as a different flexibility on top of "classical" OOP. Of cource we could call different versions of
injectedAction
if we make it a virtual function, so it will be different through
late binding, but it should be a method of a class from the same class hierarchy. The delegate is more of ad-hoc, but more flexible method.
There are a lot of uses of this behefit. Most basic is passing a delegate to provide an arbitrary predicate method to some complex method, for example, traversing some data:
void TraverseData(
MyDataContainer data,
Func<DataElement> predicate,
Action<DataElement> dataProcessor) {
DateElement element =
if (predicate(element))
dataProcessor(element);
}
Having, let's say two different algorithm for data traversal, two different predicates and two different data processors, without delegates to combine all 8 cases would have to write down 8 different versions of
TraverseData
, loosing all possible code reuse. That's why treating methods as first-class citizens (in the form of delegate instances) is so important.
For another interesting example, see my small article in Tips/Tricks section:
Hide Ad-hoc Methods Inside the Calling Method’s Body[
^].
See also my other answer on the role of
this
:
static functions[
^]. It has nothing to do with delegates but provides some clarification on instance vs. static methods.
Further reading should probably needs understanding
Closures:
http://en.wikipedia.org/wiki/Closure_(computer_science)[
^] and then
lambda expressions, as they are understood in .NET:
http://en.wikipedia.org/wiki/Functional_programming[
^].
[EDIT]
For advanced use of delegates see my recent article:
Dynamic Method Dispatcher[
^]
and a short Tips/Trick article:
Simple Blocking Queue for Thread Communication and Inter-thread Invocation[
^].
—SA