Multicast delegate weird behavior in C#?
Asked Answered
V

4

0

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 :

enter image description here

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 :

enter image description here

Veasey answered 6/12, 2012 at 16:46 Comment(17)
There is no array-of-delegate here.Urethra
The invocation list is a array of delegate.Veasey
It is some sort of collection. You can get it as an array but your code doesn't. Just a hint to pick a better title.Urethra
It is called a "multicast delegate", not "array of delegate".Hotze
@CoryNelson I was talking about the type of the invocation list which is an ARRAY ( of delegate). SEE MY EDITVeasey
@HenkHolterman to prevent confusion i change the title to Multicast delegate.Veasey
You assert that this is weird behavior. What would you expect the behavior to be? Other than ignoring the methods and just returning a default value, or returning an entirely random/arbitrary value, I can't think of what else it could possibly do.Mangrove
@Mangrove this question was written without knowing for sure that it returns the last value.Veasey
@RoyiNamir So then what's "weird" about it? For something to be "weird" it needs to do something other than what you expected it to do. What did you expect it to do? If you expected it to do this, then it's not "weird".Mangrove
@Mangrove I didnt expect it to decide which value to show/return. ( all or nothing). but I guess (as you mentioned) it only return the last.Veasey
@RoyiNamir How could it return them all? It wouldn't be of the right type for it to return all of them, it would need to be some sort of collection to do that, and then it wouldn't be matching it's own signature.Mangrove
@Mangrove Do You read my question ? I didn't expect console.write to iterate through results . but also didn't expect him to decide which to show. all or nothing is a phrase. I didnt expect one result to be returned - whereas I have 2 registered functions. Thats all.Veasey
@RoyiNamir Yes, you didn't expect it to do what it did, I got that. I was asking what you did expect it to do, not what you didn't expect it to do.Mangrove
@Mangrove No you Didn't got that - cause If you ask me : it would need to be some sort of collection to do that - then you didnt read the whole question.Again I was expeting from him NOT TO DECIDE which value to return. I think iwas pretty clear about that. leave this now.Veasey
@RoyiNamir So by not deciding did you expect it to choose randomly? Did you expect it to return null? Did you expect it to return a list/collection of values? Did you expect there to be no defined behavior for which one is returned? All of those seem like possible options given the question.Mangrove
@Mangrove I have no answer of what I was expecting to see. I only have an answer ( currently) of what i was not expecting to see - and so I wanted to learn about this behaviour. that's' all. That was the reason for this question. Cause I didnt know what to expect.Veasey
Related: multicast-delegates-must-have-a-return-type-of-void-whyAzores
A
7

There are three aspects at play here:

  1. The implementation of the event
  2. The behaviour of delegate combination
  3. 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.

Angular answered 7/12, 2012 at 9:31 Comment(0)
M
4

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.

Mangrove answered 6/12, 2012 at 16:49 Comment(10)
how does answers my question : why only one value is shown ? and why did he decide which will be visible ?Veasey
@RoyiNamir According to this post It will always return the value from the last delegate in the list, but my point is that you shouldn't ever rely on that in the first place. It won't be in the C# language specs, it's an implementation detail.Mangrove
It is in the (C# or CLR) specs. But indeed, don't ever rely on it.Urethra
social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/…Veasey
@RoyiNamir So now we have two unofficial sources stating an implementation detail. That doesn't change the fact that you shouldn't be using return values from events.Mangrove
Agree. but if @marc is right , i can count on : the return value to be from the last registered method.Veasey
@RoyiNamir "I'm fairly confident that" is just that, an educated guess. Mostly because there's not much else that it could possibly do. And, once again, what would you possibly do with that knowledge? What's the value in knowing something that you shouldn't use to begin with?Mangrove
@Mangrove to know that the invocation list is executing or not the same order is (for me) very important.not all people in the world are the sameVeasey
@RoyiNamir So you plan to write code relying on that fact? If you do, I highly suggest that you not. For starters, if you don't find an entry in the C# specs stating that it's the case then it's an implementation detail subject to change, and more importantly, it would result in highly confusing code that's going to be a nightmare to maintain as it's breaking assumptions that many developers will have about how events are used (namely that there is no reliance on order of execution).Mangrove
@Mangrove Thanks. I think I should ask a new question about it. ( order of execution. - just for knowledge.)Veasey
U
4

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.

Urethra answered 6/12, 2012 at 16:51 Comment(15)
You have very little control over who is first or last ? the way they was appended - this will be the order no ?Veasey
At first yes. But after you have removed and added some handlers it becomes harder to predict, it might even be Implementation Defined.Urethra
Invoking a multicast non-void delegate returns the value of the last handler that was executed. but you have no way of knowing if any particular event is implemented in that manor. The event could just as easily have custom add/remove methods that do...anything.Mangrove
@Mangrove - add/remove are involved in constructing the list. Not in executing the event. The rule stands.Urethra
@HenkHolterman so how can I assure that time ordered registered methods will executed at the same order ?Veasey
@HenkHolterman Yes, I agree that the statement is correct, and stands. I'm saying it may not apply to a particular situation though. Knowing how a multicast delegate invokes it's methods is irrelevant if you're using an event that just doesn't use a multicast delegate.Mangrove
it seems @marc has already answered that before social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/…Veasey
Am not sure if you can. I am sure you shouldn't even try. You can create an manage your own list-of-delegate if you really need this.Urethra
@Mangrove - every event uses a multicast delegate.Urethra
@HenkHolterman Every auto-implemented event does. If you use your own add/remove methods then I don't see what would force you to use a multicast delegate.Mangrove
@HenkHolterman Thanks. Can you please provide a way of thinking how to control the execution list order ?Veasey
@RoyiNamir - by avoiding event all together and write your own dispatcher class. You could even specify (with a lambda) how to process (sum/average) the results.Urethra
With a field-like event, the delegates will be invoked in subscription order - it's all well-defined. For a more general event, that's not the case of course - there's no guarantee that an event will provide sensible add/remove implementations at all... but I'd be very surprised to see one which didn't. Of course, invocation isn't part of what you can do with an event anyway (in C#)...Angular
Wow, saying you have no control unless you avoid event? That's very wrong. You don't have to just blindly invoke the delegate stored in the event, you can call GetInvocationList() and do any averaging you want, and still be an event. An auto-implemented (field-like) event, even.Reannareap
@BenVoigt - and do you know of a practical application for non-void delegates? Would you use it?Urethra
S
3

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);
        }
Selachian answered 6/12, 2012 at 16:58 Comment(3)
this is not what Henk says.Veasey
@RoyiNamir How so? I see no contradictions between the two.Mangrove
he says the invocation order could be not the same as it was appendedVeasey

© 2022 - 2024 — McMap. All rights reserved.