Code for this post can be found here.
Andrea Saltarello, in his post of some days ago, told about POCO object and dynamic implementation of INotifyPropertyChanged.
The problem is, how to implement INotifyPropertyChanged with dynamic code generation? The answer is that is quite simple even if we do not relay on Castle.DynamicProxy, here is a simple domain class.
public class Customer { public virtual String Property { get { return property; } set { property = value; } } private String property; public virtual Int32 AnotherProp { get { return anotherProp; } set { anotherProp = value; } } private Int32 anotherProp; }
Now let’s see how you can create a dynamic type that inherits from it, and overrides all virtual properties implementing the INotifyPropertyChanged interface. The first step is to define the event PropertyChanged, so you need three methods, one for the add an event listener, the other for removing the event listener and the final one to raise the event. First step, create all the method info I’ll need for dynamic generation
MethodInfo DelegateCombine = typeof(Delegate).GetMethod("Combine", new Type[] { typeof(Delegate), typeof(Delegate) }); MethodInfo DelegateRemove = typeof(Delegate).GetMethod("Remove", new Type[] { typeof(Delegate), typeof(Delegate) }); MethodInfo InvokeDelegate = typeof (PropertyChangedEventHandler).GetMethod("Invoke"); FieldBuilder eventBack = mTypeBuilder.DefineField("PropertyChanged", typeof(PropertyChangingEventHandler), FieldAttributes.Private); ConstructorInfo CreateEventArgs = typeof (PropertyChangingEventArgs).GetConstructor(new Type[] {typeof (String)});
I need Delegate.Combine and Delegate.Remove, then I need the invoke method of the PropertyChangedEventHandler, then I need to create a field named PropertyChanged used to store the delegate and finally I need the constructorInfo for the PropertyChangingEventArgs. Now, armed with these, I begin to create the event.
1 MethodBuilder AddPropertyChanged = mTypeBuilder.DefineMethod( 2 "add_PropertyChanged", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot, 3 typeof(void), new Type[] { typeof(PropertyChangedEventHandler) }); 4 ILGenerator gen = AddPropertyChanged.GetILGenerator(); 5 gen.Emit(OpCodes.Ldarg_0); 6 gen.Emit(OpCodes.Ldarg_0); 7 gen.Emit(OpCodes.Ldfld, eventBack); 8 gen.Emit(OpCodes.Ldarg_1); 9 gen.Emit(OpCodes.Call, DelegateCombine); 10 gen.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler)); 11 gen.Emit(OpCodes.Stfld, eventBack); 12 gen.Emit(OpCodes.Ret);
This is the code called when an object register a delegate to listen to the event. The IL code is really simple, in line 5 ad 6 load two times the instance of the object on the stack, with line 7 I push on the stack the content of the field storing the actual delegate, then in line 8 I push on the stack the new handler, and then call DelegateCombine. The result was cast to the right type and finally in line 11 I can store the new delegate in the field.
The remove part of the event is similar, it does not worth explanation. The third and final part of the event is the method called to raise the event itself.
1 MethodBuilder RaisePropertyChanged = mTypeBuilder.DefineMethod( 2 "OnPropertyChanged", MethodAttributes.Public, 3 typeof(void), new Type[] { typeof(String) }); 4 gen = RaisePropertyChanged.GetILGenerator(); 5 Label lblDelegateOk = gen.DefineLabel(); 6 gen.DeclareLocal(typeof(PropertyChangedEventHandler)); 7 gen.Emit(OpCodes.Nop); 8 gen.Emit(OpCodes.Ldarg_0); 9 gen.Emit(OpCodes.Ldfld, eventBack); 10 gen.Emit(OpCodes.Stloc_0); 11 gen.Emit(OpCodes.Ldloc_0); 12 gen.Emit(OpCodes.Ldnull); 13 gen.Emit(OpCodes.Ceq); 14 gen.Emit(OpCodes.Brtrue, lblDelegateOk); 15 gen.Emit(OpCodes.Ldloc_0); 16 gen.Emit(OpCodes.Ldarg_0); 17 gen.Emit(OpCodes.Ldarg_1); 18 gen.Emit(OpCodes.Newobj, CreateEventArgs); 19 gen.Emit(OpCodes.Callvirt, InvokeDelegate); 20 gen.MarkLabel(lblDelegateOk); 21 gen.Emit(OpCodes.Ret);
This method is a little more complex, but essentially it store the value of the actual delegate in a local variable, then compare it to null to check if someone is interested in the event. If the event handler is not null it simply invoke it, actually raising the event. Now the work is half done, we need to subclass each virtual property, calling this method to raise the event.
foreach (PropertyInfo pinfo in parent.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (pinfo.GetSetMethod().IsVirtual) { PropertyBuilder pb = mTypeBuilder.DefineProperty( pinfo.Name, PropertyAttributes.None, pinfo.PropertyType, Type.EmptyTypes); MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual; MethodBuilder getMethod = mTypeBuilder.DefineMethod( "get_" + pinfo.Name, getSetAttr, pinfo.PropertyType, Type.EmptyTypes); ILGenerator gen = getMethod.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Call, pinfo.GetGetMethod()); gen.Emit(OpCodes.Ret); pb.SetGetMethod(getMethod); MethodBuilder setMethod = mTypeBuilder.DefineMethod( "set_" + pinfo.Name, getSetAttr, null, new Type[] { pinfo.PropertyType }); gen = setMethod.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldstr, pinfo.Name); gen.Emit(OpCodes.Call, raiseEvent); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Call, pinfo.GetSetMethod()); gen.Emit(OpCodes.Ret); pb.SetSetMethod(setMethod); } }
This method cycles through all properties, check if the property is virtual, then declare a corresponding property with MethodAttributes.Virtual, actually overriding each virtual property. The get part is really simple, I call the base GetMethod of the class, but in the Set part I first call the raiseEvent (It is loaded with the MethodBuilder of OnPropertyChanged generated method), then I call the setter part of the base class.
The result permits me to run this code.
INPCEmit emitter = new INPCEmit(); Type generated = emitter.CreateType("ATEST", typeof(Customer)); Object instance = Activator.CreateInstance(generated); Customer c = (Customer)instance; c.Property = "TEST"; INotifyPropertyChanged inpc = (INotifyPropertyChanged)instance; inpc.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args) { Console.WriteLine("PropertyChanged:" + args.PropertyName); }; c.Property = "TEST"; c.AnotherProp = 22;
As you can see I generate the dynamic type with the methods I explained before, then I can create an instance with Activator.CreateInstance, then I can cast it to a Customer (it is a derived class), but I can cast also to a INotifyPropertyChanged and register for the event. The output shows that the dynamic class correctly implements INotifyPropertyChanged
All the code can be found here.
alk.
Tags: Reflection.Emit INotifyPropertyChanged Dynamic code generation
>
August 26th, 2008 at 12:47 pm
I am curious if its possible to port this code to Silverlight. It would be HUGE if you could. I’ve tried a dump port and it doesn’t work, but I don’t really know what you are doing here. If you could try it…HUGE
August 27th, 2008 at 4:41 am
I do not think that dynamic code generation is the solution to implement interfaces such as INOtiryPropertyChabnged, this post is intendend mainly to show how technically you can do this.
I had little experiences with silverlight. but I setup a simple scenario
1) a WCF service that exposes all the functions needed to manage the Domain, all the objects are DTO.
2) I create a service reference from silverlight (This in turn creates proxy for the DTO that implements IPRopertyChanges
3) I setup silverlight application to work with dto, the user modify the objects, does operations and whenever he want he can press save.
4) The save button simply takes all the DTO and send them to the appropriate update function in the service.
5) The service does all the logic to update data.
Moreover silverlight runs in browser, and it does not have full trust, so certain operations are not permitted, I think code generation is one of these :(, but I have not tried.
Alk.