Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use the EventBuilder to create an event?

Tags:

c#

.net

clr

Suppose I wanted to emit

public event PropertyChangedHandler PropertyNameChanged;

How would I do this? Would I need to define a backing field like I would a property? I can't find a single example of how to use the EventBuilder and how do I actually raise an event?

like image 957
blue18hutthutt Avatar asked Nov 29 '12 05:11

blue18hutthutt


2 Answers

I know that this is old question and this answer will probably never be accepted, but I get here from Google so probably other people will get here to. Just for those people, here is a correct answer: To create and event you have to:

  1. Create Field {EventName}
  2. Create Event with the same name {EventName}
  3. Create add and remove accessors (methods) add_{EventName} and remove_{EventName}
  4. Create raise method with some arbitrary name (or do not, since this step is optional)

Second and forth is covered by Dmitry answer, but here is a complete code. Of course you have to change EventName and EventType for values correct for your event.

  1. Field for event

var field = typeBuilder.DefineField("{EventName}", typeof({EventType}), FieldAttributes.Private);
  1. Event

var eventInfo = typeBuilder.DefineEvent("{EventName}", EventAttributes.None, typeof({EventType}));
  1. Add accessor and remove accessor; they are very similar.

    var addMethod = typeBuilder.DefineMethod("add_{EventName}",
        MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot,
        CallingConventions.Standard | CallingConventions.HasThis,
        typeof(void),
        new[] { typeof({EventType}) });
    var generator = addMethod.GetILGenerator();
    var combine = typeof(Delegate).GetMethod("Combine", new[] { typeof(Delegate), typeof(Delegate) });
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldfld, field);
    generator.Emit(OpCodes.Ldarg_1);
    generator.Emit(OpCodes.Call, combine);
    generator.Emit(OpCodes.Castclass, typeof({EventType}));
    generator.Emit(OpCodes.Stfld, field);
    generator.Emit(OpCodes.Ret);
    eventInfo.SetAddOnMethod(addMethod);

    var removeMethod = typeBuilder.DefineMethod("remove_{EventName}",
            MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot,
            CallingConventions.Standard | CallingConventions.HasThis,
            typeof(void),
            new[] { typeof({EventType}) });
    var remove = typeof(Delegate).GetMethod("Remove", new[] { typeof(Delegate), typeof(Delegate) });
    var generator = removeMethod.GetILGenerator();
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldfld, field);
    generator.Emit(OpCodes.Ldarg_1);
    generator.Emit(OpCodes.Call, remove);
    generator.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
    generator.Emit(OpCodes.Stfld, field);
    generator.Emit(OpCodes.Ret);
    eventInfo.SetRemoveOnMethod(removeMethod);
  2. Raise method. Not required, but there is really no point for creating event if you do not have method to raise it. Unless you are doing some hocus-pocus with reflection or calling value of event Delegate. Anyway here is the code for this method. Of course you have to obtain correct constructor of some XXXEventArgs type, that is used inside of event, first. Name of this raise method also may be different, but following some pattern is generally better idea (like below):

    var methodBuilder = typeBuilder.DefineMethod("On{EventName}",
        MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig |
        MethodAttributes.NewSlot, typeof(void),
        new[] { typeof(string) });
    var generator = methodBuilder.GetILGenerator();
    var returnLabel = generator.DefineLabel();
    var eventArgsCtor = typeof({XXXEventArgs}).GetConstructor(new[] { typeof(string) });
    generator.DeclareLocal(typeof({EventType}));
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldfld, field);
    generator.Emit(OpCodes.Stloc_0);
    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Brfalse, returnLabel);
    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldarg_1);
    generator.Emit(OpCodes.Newobj, eventArgsCtor);
    generator.Emit(OpCodes.Callvirt, typeof(PropertyChangedEventHandler).GetMethod("Invoke"));
    generator.MarkLabel(returnLabel);
    generator.Emit(OpCodes.Ret);
    eventInfo.SetRaiseMethod(methodBuilder);
    return methodBuilder;

    Also this method do not have be virtual, or protected (MethodAttributes.Family), but it is better to have this method non-public and overridable. Types of parameter also have to be different and compatible with XXXEventArgs type constructor. You can also have more parameters in this method but I advice against it since it makes IL more complicated (you should do as little in IL as possible as favor for yourself and your sanity).

like image 68
n.podbielski Avatar answered Sep 27 '22 19:09

n.podbielski


  TypeBuilder myClass =
     myModule.DefineType("MyClass", TypeAttributes.Public);

  MethodBuilder onPropertyNameChanged= myClass.DefineMethod("OnPropertyNameChanged",
     MethodAttributes.Public, typeof(void), new Type[]{typeof(Object)});
  ILGenerator onPropertyNameChangedIl= onPropertyNameChanged.GetILGenerator();
  onPropertyNameChangedIl.Emit(OpCodes.Ret);

  // Create the event.
  EventBuilder propertyNameChanged= myClass.DefineEvent("PropertyNameChanged", EventAttributes.None,
     typeof(PropertyChangedHandler)); //should be declared before
  propertyNameChanged.SetRaiseMethod(onPropertyNameChanged);

  myClass.CreateType();
like image 36
Dzmitry Martavoi Avatar answered Sep 27 '22 18:09

Dzmitry Martavoi