In-Memory Run-Time Code Generation With .NET – Part 4

This article is split into 5 posts.

  1. Introduction
  2. Templating Engines
  3. Code Generation From Source
  4. Code Generation From Model
  5. Loading

In-Memory Run-Time Code Generation With .NET – Part 4

Code Generation From Model

Instead of using templaing engines to create source code that need compilation, MSIL can be created using either Reflection Emit or by constructing Linq Expression Trees and compiling those.

Reflection Emit

A more direct approach to generating types to runtime is to directly generate MSIL code. The .NET Framework supplies all nesseccary classes to do so. This approach is very fast, since no source code must be parsed and compiled. On the other hand, the MSIL output must be hand-coded, which is a difficult and error-prone venture. The following class generates the types that were generated by the code templates in the previouse sections. To abbreviate the lengthy code, the generation of properties that return view model lists is skipped.

internal class TypeGenerator
{
    private readonly TypeGenerationModel _model;
    private readonly Dictionary<Type, Type> _viewModelTypes;
    private readonly TypeBuilder _typeBuilder;

    public Type GeneratedType { get; private set; }

    public static Assembly Generate(IEnumerable<TypeGenerationModel> models)
    {
        var appDomain = AppDomain.CurrentDomain;
        var assemblyName = new AssemblyName("dynamicassembly");
        var assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll");
        var viewModelTypes = new Dictionary<Type, Type>();

        foreach (var model in models.Reverse())
        {
            var generator = new Typeil(moduleBuilder, model, viewModelTypes);
            viewModelTypes.Add(model.ModelType, generator.GeneratedType);
        }

        return assemblyBuilder;
    }

    private TypeGenerator(ModuleBuilder moduleBuilder, TypeGenerationModel model, Dictionary<Type, Type> viewModelTypes)
    {
        var baseType = typeof (ViewModelBase<>).MakeGenericType(model.ModelType);
        _model = model;
        _viewModelTypes = viewModelTypes;
        _typeBuilder = moduleBuilder.DefineType(_model.Namespace + "." + _model.Name, TypeAttributes.Public, baseType);

        GeneratedType = Generate();
    }

    private Type Generate()
    {
        //
        // Create constructor
        //
        var baseConstructor = _typeBuilder.BaseType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] {_model.ModelType}, new ParameterModifier[0]);
        var constructorBuilder = _typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] {_model.ModelType});
        var il = constructorBuilder.GetILGenerator();

        il.Emit(OpCodes.Ldarg_0);                   // Load the address of "this" onto the stack
        il.Emit(OpCodes.Ldarg_1);                   // Load the first parameter onto the stack
        il.Emit(OpCodes.Call, baseConstructor);     // Call the constructor of the base class
        il.Emit(OpCodes.Ret);                       // Return

        //
        // Create properties
        //
        foreach (var property in _model.Properties)
        {
            CreateProperty(property, _typeBuilder);
        }

        foreach (var property in _model.ViewModelProperties)
        {
            CreateViewModelProperty(property, _typeBuilder);
        }
        
        // In this sample, we wont generate this kind of property.
        //foreach (var property in _model.ViewModelListProperties)
        //{
        //    CreateViewModelListProperty(property, typeBuilder);
        //}

        //
        // Finish up
        //
        return _typeBuilder.CreateType();
    }

    private void CreateProperty(PropertyGenerationModel property, TypeBuilder typeBuilder)
    {
        var propertyType = property.ElementType;
        var propertyBuilder = typeBuilder.DefineProperty(property.Name, PropertyAttributes.None,
                                                         propertyType, null);

        if (property.CanGet)
        {
            var thisModelGetter = typeBuilder.BaseType.GetProperty("Model").GetGetMethod();
            var modelPropertyGetter = _model.ModelType.GetProperty(property.Name).GetGetMethod();

            CreateGetMethod(property, typeBuilder, propertyBuilder, propertyType, il =>
            {
                // return Model.PROPERTY;
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Call, thisModelGetter);         // Call "this"."get_Model()"
                il.Emit(OpCodes.Callvirt, modelPropertyGetter); // Call "this"."get_Model()".

                // "get_PROPERTY()"
                il.Emit(OpCodes.Ret);                           // Return
            });
        }

        if (property.CanSet)
        {
            var thisModelGetter = typeBuilder.BaseType.GetProperty("Model").GetGetMethod();
            var modelPropertyGetter = _model.ModelType.GetProperty(property.Name).GetGetMethod();
            var modelPropertySetter = _model.ModelType.GetProperty(property.Name).GetSetMethod();
            var notificationMethod = typeBuilder.BaseType.GetMethod("OnPropertyChanged", BindingFlags.NonPublic | BindingFlags.Instance);

            CreateSetMethod(property, typeBuilder, propertyBuilder, propertyType, il =>
            {
                var labelElse = il.DefineLabel();

                // if(this.Model.PROPERTY == value) return;
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Call, thisModelGetter);         // Call "this"."get_Model()"
                il.Emit(OpCodes.Callvirt, modelPropertyGetter); // Call "this"."get_PROPERTY()".

                // "get_PROPERTY()"
                il.Emit(OpCodes.Ldarg_1);                       // Load the parameter onto the stack
                il.Emit(OpCodes.Bne_Un_S, labelElse);           // If the last both values on the stack are not equal, jump to the label
                il.Emit(OpCodes.Ret);                           // Return
                il.MarkLabel(labelElse);

                // this.Model.PROPERTY = value;
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Call, thisModelGetter);         // Call "this"."get_Model()"
                il.Emit(OpCodes.Ldarg_1);                       // Load the parameter onto the stack
                il.Emit(OpCodes.Callvirt, modelPropertySetter); // Call "this"."set_PROPERTY()"

                // OnPropertyChanged("PROPERTY");
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Ldstr, property.Name);          // Load the string onto the stack
                il.Emit(OpCodes.Call, notificationMethod);      // Call OnPropertyChanged()

                il.Emit(OpCodes.Ret);                           // Return
            });
        }
    }

    private void CreateViewModelProperty(PropertyGenerationModel property, TypeBuilder typeBuilder)
    {
        var propertyType = _viewModelTypes[property.ElementType];
        var fieldBuilder = typeBuilder.DefineField("_" + property.Name, propertyType, FieldAttributes.Private);
        var propertyBuilder = typeBuilder.DefineProperty(property.Name, PropertyAttributes.None, propertyType, null);

        if (property.CanGet)
        {
            // Get the view _model constructor that takes only the _model as parameter

            var ctor = propertyType.GetConstructor(new[] {property.ElementType});
            var thisModelGetter = typeBuilder.BaseType.GetProperty("Model").GetGetMethod();
            var modelPropertyGetter = _model.ModelType.GetProperty(property.Name).GetGetMethod();

            CreateGetMethod(property, typeBuilder, propertyBuilder, propertyType, il =>
            {
                var labelElse = il.DefineLabel();

                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Ldfld, fieldBuilder);           // Get the value of "this"."_field" and push in onto the stack
                                                                                            
                // if(this._field == null)
                il.Emit(OpCodes.Brtrue_S, labelElse);           // If the value on the stack is not null, jump to the label
                // {
                // this._field = new ViewModel(this.Model.Property);
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Call, thisModelGetter);         // Call "this"."get_Model()"
                il.Emit(OpCodes.Callvirt, modelPropertyGetter); // Call "this"."Model"."get_PROPERTY()"
                il.Emit(OpCodes.Newobj, ctor);                  // Create a new instance of the view model and push it onto the stack
                il.Emit(OpCodes.Stfld, fieldBuilder);           // Replace "this"."_field" by the value on the stack
                // }
                il.MarkLabel(labelElse);

                // Return this._field;
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Ldfld, fieldBuilder);           // Get the value of "this"."_field" and push it onto the stack

                il.Emit(OpCodes.Ret);                           // Return
            });
        }

        if (property.CanSet)
        {
            var valueModelGetter = propertyType.BaseType.GetProperty("Model").GetGetMethod();
            var modelPropertySetter = typeBuilder.BaseType.GetProperty("Model").PropertyType.GetProperty(property.Name).GetSetMethod();
            var thisModelGetter = typeBuilder.BaseType.GetProperty("Model").GetGetMethod();
            var notificationMethod = typeBuilder.BaseType.GetMethod("OnPropertyChanged", BindingFlags.NonPublic | BindingFlags.Instance);

            CreateSetMethod(property, typeBuilder, propertyBuilder, propertyType, il =>
            {
                var labelElse = il.DefineLabel();

                // if(this._field == value) return;
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Ldfld, fieldBuilder);           // Get the value of "this"."_field" and push in onto the stack
                il.Emit(OpCodes.Ldarg_1);                       // Load the first parameter onto the stack
                il.Emit(OpCodes.Bne_Un_S, labelElse);           // If the last both values on the stack are not equal, jump to the label
                il.Emit(OpCodes.Ret);                           // Return
                il.MarkLabel(labelElse);

                // this._field = value;
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Ldarg_1);                       // Load the parameter onto the stack
                il.Emit(OpCodes.Stfld, fieldBuilder);           // Replace "this"."_field" by the value on the stack

                // this.Model.PROPERTY = value.Model;
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Call, thisModelGetter);         // Call "this"."get_Model()"
                il.Emit(OpCodes.Ldarg_1);                       // Load the parameter ("value") onto the stack
                il.Emit(OpCodes.Callvirt, valueModelGetter);    // Call "value"."get_Model()"
                il.Emit(OpCodes.Callvirt, modelPropertySetter); // Call "this"."Model"."set_PROPERTY()"

                // OnPropertyChanged("PROPERTY");
                il.Emit(OpCodes.Ldarg_0);                       // Load the address of "this" onto the stack
                il.Emit(OpCodes.Ldstr, property.Name);          // Load the string onto the stack
                il.Emit(OpCodes.Call, notificationMethod);      // Call OnPropertyChanged()

                il.Emit(OpCodes.Ret);                           // Return
            });
        }
    }

    private static void CreateGetMethod(PropertyGenerationModel property, TypeBuilder typeBuilder, PropertyBuilder propertyBuilder, Type propertyType, Action<System.Reflection.Emit.ILil> emit)
    {
        var methodBuilder = typeBuilder.DefineMethod("Get_" + property.Name, MethodAttributes.Public | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
        var il = methodBuilder.GetILGenerator();
        emit(il);
        propertyBuilder.SetGetMethod(methodBuilder);
    }

    private static void CreateSetMethod(PropertyGenerationModel property, TypeBuilder typeBuilder, PropertyBuilder propertyBuilder, Type propertyType, Action<System.Reflection.Emit.ILil> emit)
    {
        var methodBuilder = typeBuilder.DefineMethod("Set_" + property.Name, MethodAttributes.Public | MethodAttributes.HideBySig, null, new[] {propertyType});
        var il = methodBuilder.GetILGenerator();
        emit(il);
        propertyBuilder.SetSetMethod(methodBuilder);
    }
}

Expression Trees

A furthur approach to generate code at runtime is the generation of Linq expression trees. Expression trees are not suitable in all cases. For example, with expression trees, the keyword “this” cannot be expressed. Also, constructors cannot be created. These two limitations prohibit using Linq expression trees to generate method bodies as the ones in the previouse section, even if Linq expressions can be compiled into types created via Reflection with the TypeBuilder class.

Dispite the limitation stated bove, Linq expresion trees can be very usefull when the generated code can run in a static methods. Consider the following method. It parses filter descriptions like “FirstName=Munir;LastName=Husseini” and creates expressions that compare the querable item’s properties with the specified values.

public static IQueryable<T> Where<T>(this IQueryable<T> query, string description)
{
    var parameter = Expression.Parameter(typeof (T), "item");
    var comparisons =
        from s in description.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries)
        let s2 = s.Split(new[] {'='}, StringSplitOptions.RemoveEmptyEntries)
        let propertyName = s2[0]
        let value = s2[1]
        let property = typeof (T).GetProperty(propertyName)
        select Expression.Equal(
            Expression.Property(parameter, property),
            Expression.Convert(Expression.Constant(value), property.PropertyType));

    var expressionStack = new Stack<Expression>(comparisons);

    while (expressionStack.Count > 1)
    {
        var left = expressionStack.Pop();
        var right = expressionStack.Pop();
        var and = Expression.And(left, right);

        expressionStack.Push(and);
    }

    var filterExpression = expressionStack.Pop();
    var lambda = Expression.Lambda<Func<T, bool>>(filterExpression, parameter);

    return query.Where(lambda);
}

Using the method above, the following two objects query2 and query3 will be equally filtered.

IQueryable<Person> query = ... // Load from database
var query2 = query.Where(p => p.FirstName == "Munir" && p.LastName == "Husseini");
var query3 = query.Where("FirstName=Munir;LastName=Husseini");

3 thoughts on “In-Memory Run-Time Code Generation With .NET – Part 4

  1. Hi Munir,
    Your articles are really interesting, thanks for that.
    Have you in plan to write the last article?
    I have followed your first tour articles, but then how can I use the generated code?
    Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s