views:

562

answers:

3

I have a class called EventConsumer which defines an event EventConsumed and a method OnEventConsumed as follows:

public event EventHandler EventConsumed;

public virtual void OnEventConsumed(object sender, EventArgs e)
{
    if (EventConsumed != null)
        EventConsumed(this, e);
}

I need to add attributes to the at OnEventConsumed runtime, so I'm generating a subclass using System.Reflection.Emit. What I want is the MSIL equivalent of this:

public override void OnEventConsumed(object sender, EventArgs e)
{
    base.OnEventConsumed(sender, e);
}

What I have so far is this:

...

MethodInfo baseMethod = typeof(EventConsumer).GetMethod("OnEventConsumed");
MethodBuilder methodBuilder = typeBuilder.DefineMethod("OnEventConsumed",
                                                       baseMethod.Attributes,
                                                       baseMethod.CallingConvention,
                                                       typeof(void),
                                                       new Type[] {typeof(object),
                                                                   typeof(EventArgs)});

ILGenerator ilGenerator = methodBuilder.GetILGenerator();

// load the first two args onto the stack
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Ldarg_2);

// call the base method
ilGenerator.EmitCall(OpCodes.Callvirt, baseMethod, new Type[0] );

// return
ilGenerator.Emit(OpCodes.Ret);

...

I create the type, create an instance of the type, and call its OnEventConsumed function, and I get:

Common Language Runtime detected an invalid program.

...which is not exactly helpful. What am I doing wrong? What's the correct MSIL to call the base class's event handler?

A: 

The using

public virtual void OnEventConsumed(object sender, EventArgs e)
{
    if (EventConsumed != null)
        EventConsumed(this, e);
}

should be

public virtual void OnEventConsumed(EventArgs e)
{
    EventHandler handler = this.EventConsumed;
    if ( null != handler ) handler( this, e );
}

.

I think, you should use a ILGenerator.EmitCalli and you should pass a type of return value ( i think null in this case ) and pass the types of arguments - i think "new Type[]{ typeof(EventArgs)}

TcKs
why the second form? The first works just as well.
Simon
It's cause the multithreading. If you check "EventConsumed != null", an another thread can be activated, remove the delegate from event, then the first thread will be activated, the "EventConsumed" will be null, and the "EventConsumed(this, e)" will fail on NullReferenceException.
TcKs
+1  A: 

I was actually really close - the problem was that I wasn't loading the 'this' argument, and that Callvirt calls the subclass method, where I actually wanted Call. So that section becomes:

// load 'this' and the first two args onto the stack
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Ldarg_2);

// call the base method
ilGenerator.EmitCall(OpCodes.Call, baseMethod, new Type[0] );

// return
ilGenerator.Emit(OpCodes.Ret);

Now it works fine.

Simon
+4  A: 

Here's the IL from a sample app:


.method public hidebysig virtual instance void OnEventConsumed(object sender, class [mscorlib]System.EventArgs e) cil managed
    {
        .maxstack 8
        L_0000: nop 
        L_0001: ldarg.0 
        L_0002: ldarg.1 
        L_0003: ldarg.2 
        L_0004: call instance void SubclassSpike.BaseClass::OnEventConsumed(object, class [mscorlib]System.EventArgs)
        L_0009: nop 
        L_000a: ret 
    }

So I think you aren't loading the instance because you aren't doing a ldarg.0

Cory Foy