I have this simple event :
public class ClassA
{
public event Func<string, int> Ev;
public int Do(string l)
{
return Ev(l);
}
}
And 2 Methods :
static int Display(string k)
{
return k.Length;
}
static int Display_2(string k)
{
return k.Length*10;
}
Im registering this event :
ClassA a = new ClassA();
a.Ev += Display;
a.Ev += Display_2;
Now , I'm executing :
Console.WriteLine(a.Do("aaa"));
the output :
What ???
he has in invocation list 2 methods ! it did run them , but why does it shows only the result from the last registration ?
Where does the result of "3"
has gone ? ( the first invocation ) ? ( although both display
+display_2
was executed... I didn't expect console.write
to iterate through results . but also didn't expect him to decide which to show.)
edit :
There are three aspects at play here:
- The implementation of the event
- The behaviour of delegate combination
- The behaviour of invoking a delegate whose invocation list has multiple entries
For point 1, you have a field-like event. Section 10.8.1 of the C# 4 spec gives an example, and states that:
Outside the declaration of the Button
class, the Click
member can be used only on the left-hand saide of the +=
and -=
operators, as in
b.Click += new EventHandler(...);
which appends a delegate to the invocation list of the Click
event
(emphasis mine). The spec also makes it clear that a field-like event creates a delegate field, which is used from within the class for invocation.
More generally (point 2), section 7.8.4 of the C# 4 spec talks about delegate combination via +
and +=
:
Delegate combination. Every delegate type implicitly provides the following predefined operator, where D
is the delegate type:
D operator +(D x, D y)
The binary +
operato performs delegate combination when both operands are of some delegate type D
. [... skip bits where x
or y
are null ...] Otherwise, the result of the operation is a new delegate that, when invoked, invokes the first operand and then invokes the second operand.
(Again, emphasis mine.)
Finally, point 3 - event invocation and return values. Section 15.4 of the C# spec states:
If the delegate invocation includes output parameters or a return value, their final value will come from the invocation of the last delegate in the list.
More generally, it depends on the event implementation. If you use an event implementation which uses the "normal" delegate combination/removal steps, everything is guaranteed. If you start writing a custom implementation which does crazy things, that's a different matter.
Invoking a multicast non-void delegate returns the value of the last handler that was executed.
You have very little control over who is first or last. It is a lousy system to use.
That is why most events and delegates return void
.
As a general rule, it doesn't make sense for events to return a value.
If you want to get information from an event handler it makes more sense for the event handlers to mutate an input parameter, or just call another method of whatever object fired the event to communicate something else.
Normally this doesn't even come up because events logically are passing information to the event handlers, and don't have any need to get information from the event handlers. It's honestly a sign of code smell. An event shouldn't care if anyone has subscribed to it, who they might be, what they might be doing, or even if there are any subscribers. Relying on a return value from them then just creates overly tight coupling.
The events are just iterated in the order they were attached. Because you are using a return value, the value you get is the last invoked.
This is not really a normal pattern for events though. You probably want something a bit more like:
public class MyEventArgs : EventArgs
{
public MyEventArgs()
{
Results = new List<int>();
}
public string InputString{get;set;}
public List<int> Results{get;set;}
}
public event EventHandler<MyEventArgs> Ev
public int Do(string l)
{
MyEventArgs e = new MyEventArgs();
e.InputString = l;
if(Ev != null) Ev(this, e);
return e.Results.Sum();
}
and then
static int Display(object sender, MyEventArgs e)
{
return e.Results.Add(k.Length);
}
static int Display_2(object sender, MyEventArgs e)
{
return e.Results.Add(k.Length*10);
}