Mobile Zone is brought to you in partnership with:

My name is Pieter De Rycke, I am a Belgian technical .Net architect working in the insurance sector. I am always eager to learn new technologies and I like to keep up with the latest innovations and trends in the IT sector. I am a big believer in web services for interoperability between platforms. Pieter is a DZone MVB and is not an employee of DZone and has posted 36 posts at DZone. You can read more from them at their website. View Full User Profile

Dynamically adding RaisePropertyChanged to MVVM Light ViewModels using Mono Cecil

07.14.2011
| 4280 views |
  • submit to reddit
Introduction

A few days ago, I wrote an article about dynamically modifying MVVM Light ViewModels using Reflection.Emit. The same can be done with the more powerful Mono Cecil library. Mono Cecil can both create assemblies at runtime and modify existing ones. The latter allows to modify post-compilation the generated MSIL code. This is helpful on platforms like Windows Phone 7; that do not allow dynamic runtime loading of assemblies.

In this article, I will show you how we can generate the same proxies as in the Reflection.Emit example. In a latter example, I will show you how to update assemblies post compilation for use on Windows Phone 7. If you have not read my previous article, I recommend that you read it first.

Implementation

Mono Cecil can be easily retrieved using NuGet.

mono_cecil_nuget

The implementation is almost the same; similar concepts classes are used. I will not explain them in detail, but just show the modified example. (If you would have questions, feel free to drop a comment Glimlach).

public static class CecilViewModelFactory
{
    public static T CreateInstance<T>()
        where T : ViewModelBase
    {
        Type vmType = typeof(T);

        ModuleDefinition module = ModuleDefinition.CreateModule("CecilDynamicTestPieter", ModuleKind.Dll);
        TypeReference baseTypeReference = module.Import(vmType);
        TypeDefinition typeDefinition =
            new TypeDefinition(vmType.Namespace, "Smart" + vmType.Name, TypeAttributes.Public, baseTypeReference);
        module.Types.Add(typeDefinition);
        MethodReference raisePropertyChangedMethod = module.Import(typeof(ViewModelBase).GetMethod("RaisePropertyChanged",
            Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance, null, new Type[] { typeof(string) }, null));

        // Create default constructor
        typeDefinition.Methods.Add(CreateDefaultConstructor(module, vmType));

        foreach (Reflection.PropertyInfo propertyInfo in FindNotifyPropertyChangCandidates<T>())
        {
            ILProcessor processor;

            // Get set method of base type
            MethodReference setMethodReference = module.Import(propertyInfo.GetSetMethod());

            PropertyDefinition propertyDefinition = new PropertyDefinition(propertyInfo.Name,
                PropertyAttributes.None, module.Import(propertyInfo.PropertyType));
            typeDefinition.Properties.Add(propertyDefinition);

            // Create set method
            MethodDefinition setMethodDefinition = new MethodDefinition("set_" + propertyInfo.Name,
                MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Public | MethodAttributes.Virtual,
                module.Import(typeof(void)));
            setMethodDefinition.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, module.Import(propertyInfo.PropertyType)));
            propertyDefinition.SetMethod = setMethodDefinition;
            typeDefinition.Methods.Add(setMethodDefinition);

            processor = setMethodDefinition.Body.GetILProcessor();

            // Add IL code for set method
            processor.Emit(OpCodes.Nop);
            processor.Emit(OpCodes.Ldarg_0);
            processor.Emit(OpCodes.Ldarg_1);
            processor.Emit(OpCodes.Call, setMethodReference);

            // Call property changed for object
            processor.Emit(OpCodes.Nop);
            processor.Emit(OpCodes.Ldarg_0);
            processor.Emit(OpCodes.Ldstr, propertyInfo.Name);
            processor.Emit(OpCodes.Callvirt, raisePropertyChangedMethod);
            processor.Emit(OpCodes.Nop);
            processor.Emit(OpCodes.Ret);
        }

        return CreateInstance<T>(module, typeDefinition);
    }

    private static MethodDefinition CreateDefaultConstructor(ModuleDefinition module, Type baseType)
    {
        MethodDefinition defaultConstructor =
            new MethodDefinition(".ctor",
                MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                module.Import(typeof(void)));
        ILProcessor processor = defaultConstructor.Body.GetILProcessor();
        processor.Emit(OpCodes.Ldarg_0);
        processor.Emit(OpCodes.Call, module.Import(baseType.GetConstructor(Type.EmptyTypes)));
        processor.Emit(OpCodes.Ret);

        return defaultConstructor;
    }

    private static IEnumerable<Reflection.PropertyInfo> FindNotifyPropertyChangCandidates<T>()
    {
        return from p in typeof(T).GetProperties()
                where p.GetSetMethod() != null && p.GetSetMethod().IsVirtual &&
                p.GetCustomAttributes(typeof(RaisePropertyChangedAttribute), false).Length > 0
                select p;
    }
}

Note: The issue I had when started using Mono Cecil, was how to load assemblies dynamically; instead of just writing them to disk. At the end, I came up with this solution.

private static T CreateInstance<T>(ModuleDefinition module, TypeDefinition typeDefinition)
{
    Type dynamicType;

    using (MemoryStream stream = new MemoryStream())
    {
        module.Write(stream);
        Reflection.Assembly assembly = Reflection.Assembly.Load(stream.ToArray());
        dynamicType = assembly.GetType(typeDefinition.FullName);
    }
    return (T)Activator.CreateInstance(dynamicType);
}

Using the CecilViewModelFactory implementation is very straightforward; dynamic ViewsModels can be created using the following pattern:

SampleViewModel viewModel = CecilViewModelFactory.CreateInstance<SampleViewModel>();
Conclusion

In this article, I have just shown the top of the iceberg of what is possible with Mono Cecil. Dynamically creating or modifying types can reduce boiler code and lead to cleaner code. I a next article, I plan to show how you can use this in WP7 applications.

References
Published at DZone with permission of Pieter De Rycke, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)