Sunday, April 17, 2011

How virtual events work in C# ?

Below is the program I used for the test. It prints (as expected):

Raise A
Event from A
Raise B
Event from B

Now, if we change first two lines of the Main to be:

        A a = new B();
        B b = new B();

the Program will print:

Raise A
Raise B
Event from B

which is also expected, as overriding event hides the private backing field in the base class and therefore events fired by the base class are not visible to clients of the derived class.

Now I am changing the same lines to:

 B b = new B();
 A a = b;

and the program starts printing:

Raise A
Raise B
Event from A
Event from B

What's going on?

class A
{
    public virtual event EventHandler VirtualEvent;
    public void RaiseA()
    {
        Console.WriteLine("Raise A");
        if (VirtualEvent != null)
        {
            VirtualEvent(this, EventArgs.Empty);
        }
    }
}
class B : A
{
    public override event EventHandler VirtualEvent;
    public void RaiseB()
    {
        Console.WriteLine("Raise B");             
        if (VirtualEvent != null)
        {
            VirtualEvent(this, EventArgs.Empty);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        B b = new B();

        a.VirtualEvent += (s, e) => Console.WriteLine("Event from A");
        b.VirtualEvent += (s, e) => Console.WriteLine("Event from B");

        a.RaiseA();
        b.RaiseB();
    }
}
From stackoverflow
  • You hooked up two event handlers to the same event. Since A and B are pointing to the same object, when you call b.RaiseB() both event handlers get fired. So first you're calling RaiseA which is a basecalss method. That prints Raise A. It then doesn't actually fire off the event because it's null. Then, you're raising B but TWO handlers are hooked up to it, therefore it first prints Raise B, and when the event fires, both handlers get called.

  • We have a single instance (of B) which has the following fields:

    • A.VirtualEvent: null
    • B.VirtualEvent: Two event handlers

    The call to a.RaiseA() just prints "Raise A" - but nothing more, because the private field in A is null.

    The call to b.RaiseB() prints the remaining three lines, because the event has been subscribed to twice (once to print "Event from A" and once to print "Event from B").

    Does that help?

    EDIT: To make it clearer - think of the virtual event as a pair of virtual methods. It's very much like this:

    public class A
    {
        private EventHandler handlerA;
    
        public virtual void AddEventHandler(EventHandler handler)
        {
            handlerA += handler;
        }
    
        public virtual void RemoveEventHandler(EventHandler handler)
        {
            handlerA -= handler;
        }
    
        // RaiseA stuff
    }
    
    public class B : A
    {
        private EventHandler handlerB;
    
        public override void AddEventHandler(EventHandler handler)
        {
            handlerB += handler;
        }
    
        public override void RemoveEventHandler(EventHandler handler)
        {
            handlerB -= handler;
        }
    
        // RaiseB stuff
    }
    

    Now is it clearer? It's not quite like that because as far as I'm aware you can't override just "part" of an event (i.e. one of the methods) but it gives the right general impression.

    Prankster : It definitely helps, except it is unclear how a.VirtualEvent += (s, e) => Console.WriteLine("Event from A"); was even able to see B.VirtualEvent to subscribe to it?
    Michael Meadows : +1, bang! that was the sound of my mind snapping back. I stretched my brain to its limit staring at OP's code trying to wrap my brain around it. Even if I saw the bug, I wouldn't have been able to word it so lucidly.
    Michael Meadows : B.VirtualEvent hides A.VirtualEvent, because events are not polymorphic.
    Prankster : @Michael Meadows: That's not what http://msdn.microsoft.com/en-us/library/ms173152(VS.80).aspx says about polymorphism.
    Michael Meadows : @prankster ok, now my brain just exploded. I never knew you could qualify events with the virtual keyword. I apologize for the misinformation.
    Jon Skeet : @prankster: a.VirtualEvent(...) was able to "see" B.VirtualEvent *precisely* because it was virtual. It was overriding A.VirtualEvent.
    Firoso : as "JS" converts to C# in realtime.
  • Try making your RaiseA function protected + virtual.

    A rule of thumb: If derived class overrides event accessors, it must also override the function that invokes the event.

    Prankster : "If derived class overrides event accessors, it must also override the function that invokes the event.". Can you point me to the section of language specification that states this, please?
    Koistya Navin : Take a look at this page: http://msdn.microsoft.com/en-us/library/8627sbea(VS.80).aspx
    Koistya Navin : There is a statement "Wrap the event in a protected virtual method to enable derived classes to raise the event."
    Koistya Navin : Also check this blog post: http://blogs.msdn.com/samng/archive/2007/11/26/virtual-events-in-c.aspx

0 comments:

Post a Comment