views:

643

answers:

3

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();
    }
}
+1  A: 

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.

BFree
+11  A: 

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.

Jon Skeet
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?
Prankster
+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.
Michael Meadows
@Michael Meadows: That's not what http://msdn.microsoft.com/en-us/library/ms173152(VS.80).aspx says about polymorphism.
Prankster
@prankster ok, now my brain just exploded. I never knew you could qualify events with the virtual keyword. I apologize for the misinformation.
Michael Meadows
@prankster: a.VirtualEvent(...) was able to "see" B.VirtualEvent *precisely* because it was virtual. It was overriding A.VirtualEvent.
Jon Skeet
as "JS" converts to C# in realtime.
Firoso
A: 

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.

Koistya Navin
"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?
Prankster
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
Koistya Navin