On the Internet, I found this code:

using System; class a { public event EventHandler Ev; public void EventStart() { EventHandler temp = Ev; if (temp != null) temp(this, EventArgs.Empty); } } class demo { static void Main() { a A = new a(); A.Ev += (o, e) => { Console.WriteLine(1); }; A.Ev += (o, e) => { Console.WriteLine(2); }; A.Ev += (o, e) => { Console.WriteLine(3); }; A.EventStart(); } } 

And I was very surprised by this line EventHandler temp = Ev; why is the assignment going fine? After all, an event and a delegate are two different things. The event is a field with add remove accessors that add / remove methods (delegates) to the "delegate" array. That is, if it is crude, then this assignment is something like Action a = new Action[n] . I was especially surprised that if you make a lot of subscriptions to this event, then for some reason, when you call one delegate, they all start. Explain please.

  • one
    so as not to call if (temp != null) you can define Ev as: public event EventHandler Ev = delegate {}; - Stack
  • @Stack assignment to a local variable and checking the variable is not done for this. - andreycha
  • @andreycha "not for this" - checking does so that there are no problems when there are different threads. and var tmp = .... if(tmp ...) is not enough. therefore, the easiest way to define is = delegate {}; - Stack
  • @Stack yes. You just wrote it as if the check is inserted only because the default field is null. And if you assign a default value, then it would not be bad and readonly still deliver. "and var tmp = .... if (tmp ...) is not enough" - why? - andreycha
  • @andreycha "why?" - from multithreading. need to put lock, see here - Stack

2 answers 2

The fact is that all delegates are descendants of MulticastDelegate . Those. each delegate is already, as you put it, a "delegate array", a container that contains a list of calls.

Further, an event is simply a member of a class that has a delegate type ( EventHandler in this case), plus some syntax that allows the subscription / unsubscription mechanism to be implemented (the same add / remove ). Given these two things, assignment is an absolutely correct action (and is done in order to prevent a NullReferenceException in a multi-threaded environment).

Regarding the call of all subscriptions. As I said, each delegate contains a list of all calls. Therefore, when an event triggers, all subscriptions are triggered. Subscriptions are called synchronously, with this the order of their call is not defined *.

I recommend reading the book "CLR via C #" by Jeffrey Richter, chapter 11, and also John Skeet's book "C # for professionals", chapter 2 ( there is a Habré translation ).


* Strictly speaking, MulticastDelegate does trigger subscriptions in the order they are added. Therefore, for field-like events, we can talk about a specific order of the call. However, this fact is part of the implementation and rely on it is not necessary. In the case of events that have add / remove accessors, it is impossible to rely on the order at all, since it is generally not known how the subscription occurs inside.

  • Thanks for the help ! - Polyakov Sergey
  • @PolyakovSergey is not for nothing! - andreycha
  • "Subscriptions are invoked synchronously, with this the order of their call is not defined" - but isn't it called in the order of their addition? one thread goes through the list and calls the handlers. or not? - Stack
  • You can call subscribers yourself. example here - Stack
  • @Stack updated the response. - andreycha

In addition to the correct answer @andreycha:

The fact is that for events defined without explicitly specifying add / remove , the internal delegate in which the list of subscribers is stored is accessible by the same name as the event from the inside of the class. Of course, only an event is available outside, otherwise it would be a violation of encapsulation.

Additional reading on the topic: https://ru.stackoverflow.com/a/226698/10105

  • Yes, but as far as I know, although I may be mistaken, this internal delegate is not simple, but an array. That is, in the context of my example EventHandler [] Ev = new [] EventHandler. That is, I see it as EventHandler Ev = new [] EventHandler which is unacceptable. - Polyakov Sergey
  • @PolyakovSergey "as far as I know, although I may be wrong" - look at the source of the GetInvocationList () method in it creates an array - Stack
  • @PolyakovSergey: This is MulticatDelegate , if I remember correctly. He has a list of them inside. - VladD