Virtual Event doesn't behave as expected.

K

Karsten Schramm

Hi,

when I run the following code:

using System;

namespace ConsoleApplication22
{
class Program
{
static void Main(string[] args)
{
BaseClass baseClass = new BaseClass();
BaseClass inheritedBaseClass = new InheritedClass();
InheritedClass inheritedClass = new InheritedClass();
baseClass.EventA += new EventHandler(baseClass_EventA);
inheritedBaseClass.EventA += new
EventHandler(inheritedBaseClass_EventA);
inheritedClass.EventA += new
EventHandler(inheritedClass_EventA);
Console.ReadLine();
}

static void inheritedClass_EventA(object sender, EventArgs e)
{
throw new Exception("The method or operation is not
implemented.");
}

static void inheritedBaseClass_EventA(object sender, EventArgs
e)
{
throw new Exception("The method or operation is not
implemented.");
}

static void baseClass_EventA(object sender, EventArgs e)
{
throw new Exception("The method or operation is not
implemented.");
}
}

class BaseClass
{
public virtual event EventHandler EventA
{
add
{
this.EventB += this.BaseClass_EventB;
this.EventC += this.BaseClass_EventC;
}

remove
{
this.EventC -= this.BaseClass_EventC;
this.EventB -= this.BaseClass_EventB;
}
}

public virtual event EventHandler EventB;

public virtual event EventHandler EventC
{
add
{
}
remove
{
}
}

void BaseClass_EventC(object sender, EventArgs e)
{
throw new Exception("The method or operation is not
implemented.");
}

void BaseClass_EventB(object sender, EventArgs e)
{
throw new Exception("The method or operation is not
implemented.");
}
}

class InheritedClass : BaseClass
{
public override event EventHandler EventB
{
add
{
Console.WriteLine("EventB: Override add called");
}

remove
{
Console.WriteLine("EventB: Override remove called");
}
}

public override event EventHandler EventC
{
add
{
Console.WriteLine("EventC: Override add called");
}
remove
{
Console.WriteLine("EventC: Override remove called");
}
}
}
}


Then only the override of EventC will be called, not the one from
EventB. Was this to be expected?

Thx Karsten
 
M

Marc Gravell

It looks like the compiler is treating "EventB" as the
compiler-provided field, not the property; this only happens within
the actual class, not sub-classes. A poor workaround is to provide
your own backing field - then later references to "EventB" mean the
event and not the field:

private EventHandler _eventB;
public virtual event EventHandler EventB {
add { _eventB += value; }
remove { _eventB -= value; }
}

The better option is (depending on usage) to rationalise how how call
events, and rather than subscribing people to events they weren't
asking for, trigger the necessary event in the typical protected
virtual On... method.

protected override OnEventA() {
base.OnEventA();
OnEventB();
OnEventC();
}

Marc
 
M

Marc Gravell

Full code:

private EventHandler _eventB;
public virtual event EventHandler EventB {
add { _eventB += value; }
remove { _eventB -= value; }
}/*Hi,

when I run the following code:
*/
using System;

namespace ConsoleApplication22
{
class Program
{
static void Main(string[] args)
{
BaseClass baseClass = new BaseClass();
BaseClass inheritedBaseClass = new InheritedClass();
InheritedClass inheritedClass = new InheritedClass();

baseClass.EventA += new EventHandler(baseClass_EventA);
baseClass.EventB += new EventHandler(baseClass_EventB);
inheritedBaseClass.EventA += new
EventHandler(inheritedBaseClass_EventA);
inheritedBaseClass.EventB += new
EventHandler(inheritedBaseClass_EventB);
inheritedClass.EventA += new
EventHandler(inheritedClass_EventA);
inheritedClass.EventB += new
EventHandler(inheritedClass_EventB);

baseClass.Foo();
inheritedBaseClass.Foo();
inheritedClass.Foo();

Console.ReadLine();
}
static void inheritedClass_EventB(object sender, EventArgs e)
{
Console.WriteLine("inheritedClass_EventB");
}
static void inheritedBaseClass_EventB(object sender, EventArgs
e) {
Console.WriteLine("inheritedBaseClass_EventB");
}
static void baseClass_EventB(object sender, EventArgs e) {
Console.WriteLine("baseClass_EventB");
}
static void inheritedClass_EventA(object sender, EventArgs e)
{
Console.WriteLine("inheritedClass_EventA");
}
static void inheritedBaseClass_EventA(object sender, EventArgs
e) {
Console.WriteLine("inheritedBaseClass_EventA");
}
static void baseClass_EventA(object sender, EventArgs e) {
Console.WriteLine("baseClass_EventA");
}
}

class BaseClass
{
public void Foo() { // do something that triggers A
OnEventA();
}
public event EventHandler EventA, EventB, EventC;
private void OnEvent(EventHandler handler) {
if (handler != null) handler(this, EventArgs.Empty);
}
protected virtual void OnEventA() { OnEvent(EventA); }
protected virtual void OnEventB() { OnEvent(EventB); }
protected virtual void OnEventC() { OnEvent(EventC); }
}

class InheritedClass : BaseClass
{
protected override void OnEventA() {
base.OnEventA();
OnEventB();
OnEventC();
}
}
}
 
M

Marc Gravell

Oops; my sample was backward; the OP would have subscribes to "A" also
get messages when "B" or "C" are fired (and oddly not A); of course
via polymoprhism this would be (including A as a minor change):

class InheritedClass : BaseClass
{
protected override void OnEventB() {
base.OnEventB();
OnEventA();
}
protected override void OnEventC() {
base.OnEventC();
OnEventA();
}
}

Marc
 
K

Karsten Schramm

Oops; my sample was backward; the OP would have subscribes to "A" also
get messages when "B" or "C" are fired (and oddly not A);

The sample was of course shortened. I was just astounded that a
virtual event differs if you use add/remove or not.
 
M

Marc Gravell

Lots of things change with add / remove versus the default
implementation... and it does make sense... from the compiler's point
of view it is reasonable to assume that (private to the class) you are
referring to the field, otherwise you would never be able to *invoke*
the event - just [un]subscribe. But I can't see any reason why (in the
*developer's* code += and -= can't be routed via the event rather than
the field (since the compiler-provided implementation would do this
anyway...) with all other access (get, set, invoke) via the field -
but then you have to start remembering special cases, which is a PITA.

Perhaps a more desirable compiler feature would be to not let you
write virtual events without providing your own backing field (or
leaving it abstract) ;-p

Marc
 
L

Laura T.

I'm a bit stumbled.

The standard says:
"
1 The accessors of an inherited virtual event can be overridden in a derived
class by including an event declaration that specifies an override modifier.
2 This is known as an overriding event declaration.
3 An overriding event declaration does not declare a new event.
4 Instead, it simply specializes the implementations of the accessors of an
existing virtual event.
"

It says that the *accessors* of an inherited virtual event can be overriden
(1), but does not state you have to "declare" the accessors too (as with
EventC) to be able to override the accessors or if I can declare just the
event as virtual (as EventB).

It seems that from the compilers point of view, the EventB is virtual, but
just a field (special one), so it generates a private field, *but* it
generates virtual Add/Remove_EventB methods too, but it does not use them
when generating rest of the code even when an inherited class overrides
them. Maybe because it does not bind automatically generated code and
manually written code?

Another thing that I don't understand is that if I change the example to
this:

class InheritedClass : BaseClass
{
public override event EventHandler EventB;
..
..
..

The compiler generates a private copy of the overridden event, IMO,
violating the rule 3:

"3 An overriding event declaration does not declare a new event. "



Karsten Schramm said:
Hi,

when I run the following code:

using System;

namespace ConsoleApplication22
{
class Program
{
static void Main(string[] args)
{
BaseClass baseClass = new BaseClass();
BaseClass inheritedBaseClass = new InheritedClass();
InheritedClass inheritedClass = new InheritedClass();
baseClass.EventA += new EventHandler(baseClass_EventA);
inheritedBaseClass.EventA += new
EventHandler(inheritedBaseClass_EventA);
inheritedClass.EventA += new
EventHandler(inheritedClass_EventA);
Console.ReadLine();
}

static void inheritedClass_EventA(object sender, EventArgs e)
{
throw new Exception("The method or operation is not
implemented.");
}

static void inheritedBaseClass_EventA(object sender, EventArgs
e)
{
throw new Exception("The method or operation is not
implemented.");
}

static void baseClass_EventA(object sender, EventArgs e)
{
throw new Exception("The method or operation is not
implemented.");
}
}

class BaseClass
{
public virtual event EventHandler EventA
{
add
{
this.EventB += this.BaseClass_EventB;
this.EventC += this.BaseClass_EventC;
}

remove
{
this.EventC -= this.BaseClass_EventC;
this.EventB -= this.BaseClass_EventB;
}
}

public virtual event EventHandler EventB;

public virtual event EventHandler EventC
{
add
{
}
remove
{
}
}

void BaseClass_EventC(object sender, EventArgs e)
{
throw new Exception("The method or operation is not
implemented.");
}

void BaseClass_EventB(object sender, EventArgs e)
{
throw new Exception("The method or operation is not
implemented.");
}
}

class InheritedClass : BaseClass
{
public override event EventHandler EventB
{
add
{
Console.WriteLine("EventB: Override add called");
}

remove
{
Console.WriteLine("EventB: Override remove called");
}
}

public override event EventHandler EventC
{
add
{
Console.WriteLine("EventC: Override add called");
}
remove
{
Console.WriteLine("EventC: Override remove called");
}
}
}
}


Then only the override of EventC will be called, not the one from
EventB. Was this to be expected?

Thx Karsten
 
M

Marc Gravell

public override event EventHandler EventB;
The compiler generates a private copy of the overridden event, IMO,
violating the rule 3:

I don't think it does... Reflector shows that such an override
declares a private field (as you expect if you don't provide an
implementation), and that it *does not* call the base implementation.
It does *not* (however) declare a new event - merely an override to
the add_Blah and remove_Blah methods. In this hybrid the only way you
could make it work is to make an OnBlah that is overridden to call the
local field... but why mess with this anyways?

I guess you could provide your own implementation calling the base and
providing your own field. In a lot of scenarios you would want to use
an EventHandlerList anyway, so the benefit of the compiler-provided
event implementation is further reduced.

Marc
 
M

Marc Gravell

retraction: looking at the IL, maybe it does declare a new event at
the class, maybe not. I'm no longer 100% sure! But either way, it/they
hook into the same virtual/overload methods, so the result is the same
(ish).

Marc
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top