Dynamically adding RaisePropertyChanged to MVVM Light ViewModels using Mono Cecil
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.
ImplementationMono Cecil can be easily retrieved using 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
).
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.
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





