beaucrawford.net

Give me data or give me death

About the author

Author Name is someone.
E-mail me Send mail

Recent comments

Don't show

Authors

Tags

Don't show

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    © Copyright 2010

    Dynamically creating an aspect using TypeBuilder

    In my last post I gave example of where you might want to dynamically create a type that implements a given interface.  Doing this is not extremely difficult but it took some digging around.  It is obviously going to involve the System.Reflection.Emit.ILGenerator class.  I am definitely not an IL guru but, with the use of Reflector, I can usually hack together what I need.  This post was close to what I wanted but it only worked for interfaces that contained properties and methods.  It did not handle indexers and event handlers.

    The most complicated piece of code involves adding IL that forwards a method call to an arbitrary method from the internal field in our “aspect”. 
    (Please note, for the sake of brevity, I have removed almost all error checking and Exception throws)


    private static MethodBuilder DefineMethod(MethodInfo methodInfo, TypeBuilder typeBuilder, FieldBuilder fieldBuilder, 
    Type objectType) { MethodAttributes attributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual; Type[] parameterTypes = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray(); MethodBuilder methodBuilder = typeBuilder.DefineMethod ( methodInfo.Name, attributes, methodInfo.ReturnType, parameterTypes ); ILGenerator generator = methodBuilder.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldfld, fieldBuilder); if (parameterTypes.Length > 0) { for (int i = 0; i < parameterTypes.Length; i++) { generator.Emit(OpCodes.Ldarg, i + 1); } } generator.EmitCall(OpCodes.Callvirt, methodInfo, null); generator.Emit(OpCodes.Ret); return methodBuilder; }


    Once we have this code in place we can then add code to dynamically forward calls for methods, properties, events, and indexers.  You should first know that properties, events, and indexers actually get compiled as methods.

    • Properites – get compiled with methods named get_PropertyName (if the property is readable) and set_PropertyName (if the property is writable)
    • Events – get compiled with two methods, namely add_EventName and remove_EventName (both always exist)
    • Indexers – are treated as a special property named “Item”, so they end up having get_Item and set_item properties.  More on this later.

    The fact that everything is actually a method greatly simplifies our code.  All we need is the respective MethodInfo instance, which we will then send to the above DefineMethod method.

    Events:

    private static void DefineEvents(Type objType, TypeBuilder typeBuilder, FieldBuilder fieldObj, Type interfaceType) 
    {
        foreach (EventInfo eventInfo in interfaceType.GetEvents()) 
        { 
            EventInfo existingEventInfo = objType.GetEvent(eventInfo.Name, BindingFlags.Public | BindingFlags.Instance); 
            
            EventBuilder eventBuilder = typeBuilder.DefineEvent(eventInfo.Name, EventAttributes.None, eventInfo.EventHandlerType); 
            
            MethodInfo eventAdd = objType.GetMethod("add_" + eventInfo.Name, BindingFlags.Public | BindingFlags.Instance); 
            
            MethodBuilder addMethod = DefineMethod(eventAdd, typeBuilder, fieldObj, objType); 
            
            eventBuilder.SetAddOnMethod(addMethod); 
            
            MethodInfo eventRemove = objType.GetMethod("remove_" + eventInfo.Name, BindingFlags.Public | BindingFlags.Instance); 
            
            MethodBuilder removeMethod = DefineMethod(eventRemove, typeBuilder, fieldObj, objType); 
            
            eventBuilder.SetRemoveOnMethod(removeMethod); 
        } 
    }

    So, here you can see that we are reaching into the Type for our internal field and grabbing the add_ and remove_ MethodInfo instances for each event handler.  From there we are simply injecting a method using our DefineMethod method that forwards the calls to our internal field.  If you were to actually type this code out it would look something like:

    public class MyClassAspect
    {    
        MyClass _field;     
    
        public event EventHandler MyEvent    
        {
            add { _field.MyEvent += value; }
            remove { _field.MyEvent -= value; }    
        }
    }

    Indexers:

    private static void DefineIndexer(PropertyInfo interfaceProperty, Type objectType, TypeBuilder typeBuilder, FieldBuilder fieldObj, 
    Type interfaceType) { Type[] parameterTypes = interfaceProperty.GetIndexParameters().Select(p => p.ParameterType).ToArray(); PropertyInfo existingProperty = objectType.GetProperty(interfaceProperty.Name, interfaceProperty.PropertyType, parameterTypes); PropertyBuilder propertyBuilder = typeBuilder.DefineProperty
    (
    interfaceProperty.Name,
    PropertyAttributes.None,
    interfaceProperty.PropertyType,
    parameterTypes
    ); if (interfaceProperty.CanRead) { MethodInfo getMethod = objectType.GetMethod("get_" + existingProperty.Name, parameterTypes); MethodBuilder getMethodBuilder = DefineMethod
    (
    getMethod,
    typeBuilder,
    fieldObj,
    objectType
    );


    propertyBuilder.SetGetMethod(getMethodBuilder); } if (interfaceProperty.CanWrite) { List<Type> setMethodParameters = new List<Type>(parameterTypes); setMethodParameters.Add(interfaceProperty.PropertyType); MethodInfo setMethod = objectType.GetMethod("set_" + existingProperty.Name, setMethodParameters.ToArray()); MethodBuilder setMethodBuilder = DefineMethod(setMethod, typeBuilder, fieldObj, objectType); propertyBuilder.SetSetMethod(setMethodBuilder); } }

    Forwarding the calls for indexers is similar to events except that the methods are prefixed get_ and set_.  Two things initially threw me for a loop here:

    • For indexers, the compiler adds a property named “Item”.  This means that you have methods name get_Item and set_Item. Interestingly enough, if you add your own property named “Item” to a class and then try to add an indexer you will get a compile error telling you that a property named “Item” already exists.
    • For the set_Item method, the compiler adds the incoming value as the last parameter of the method.  For example, although not very common, you can have indexers with multiple parameters.  An interface signature for one might look like:
    public interface IExample
    {
        string this[string x, Guid y, int z]    
        {
            get;
            set;    
        }
    }

    If you look at this interface in Reflector you will see that the compiler adds the Item property, along with the get_Item and set_Item methods:

     

    So, you can see the set_Item method actually has four parameters.  If you look in the above DefineIndexer method you will notice that I tack the indexer’s return type onto the parameter signature before attempting to reference the existing method in the internal field.  Another important note is that, for indexers, the compiler adds an instance of the System.Reflection.DefaultMemberAttribute attribute to the class to associate the default member with the Item property.

    Next time I will cover properties and methods, and provide the full code for the DynamicAspect class.


    Posted by beau on Monday, June 16, 2008 8:03 PM
    Permalink | Comments (0) | Post RSSRSS comment feed

    Comments