Thursday, May 31, 2007

.NET Class Generation - Delegating Object Properties

In a recent assignment I had the need for a dynamically generated class that - among other things - could be generated with a field of arbitrary type and delegate the properties of said field. In other words something like

public class MyWrapperClass : MyParentClass
{
private ObjectType _WrappedObject;

public ObjectType WrappedObject
{
set { _WrappedObject = value; }
}

public string Field1
{
get { return _WrappedObject.Field1; }
}

public int Field2
{
get { return _WrappedObject.Field2; }
}

// ... other stuff
}
where ObjectType can be any type and MyParentClass is a base class.

Fortunately, .NET reflection and class generation is pretty straight-forward. Generating the class itself requires an assembly, module etc.:

AppDomain appDomain = Thread.GetDomain();
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "MyAssembly";
AssemblyBuilder assemblyBuilder
= appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MyModule");
TypeBuilder typeBuilder
= moduleBuilder.DefineType("MyWrapperClass", TypeAttributes.Public);
typeBuilder.SetParent(typeof(MyParentClass));


The field holding our wrapped object must be declared to be the same type as the object to be wrapped

private ObjectType _ObjectToBeWrapped


This can be done with the following code:

Type objectType = wrappedObject.GetType();
FieldBuilder wrappedObjectField
= typeBuilder.DefineField("_WrappedObject", objectType, FieldAttributes.Private);
We will also need a setter for the object

public ObjectType WrappedObject { set { _WrappedObject = value; } }
And this requires some IL code generation:

// Create property ObjectToBeWrapped
PropertyBuilder wrappedObjPropertyBuilder
= typeBuilder.DefineProperty("WrappedObject", PropertyAttributes.None,
objectType, null);

// Create property setter
MethodBuilder wrappedObjSetBuilder =
typeBuilder.DefineMethod("set_WrappedObject", MethodAttributes.Public,
null, new Type[] { objectType });
ILGenerator wrappedObjSetILG = wrappedObjSetBuilder.GetILGenerator();
wrappedObjSetILG.Emit(OpCodes.Ldarg_0);
wrappedObjSetILG.Emit(OpCodes.Ldarg_1);
wrappedObjSetILG.Emit(OpCodes.Stfld, wrappedObjectField);
wrappedObjSetILG.Emit(OpCodes.Ret);
wrappedObjPropertyBuilder.SetSetMethod(wrappedObjSetBuilder);
Note that the "set { value = .. }" notation actually corresponds to a method "set_MyProperty(object value) {...", much like Java-style getters & setters.

Now for the fun bit: Create a getter for each property in the wrapped object. We iterate through the properties and generating a getter method works much like the setter above.

foreach (PropertyInfo propertyInfo in objectType.GetProperties())
{
string name = propertyInfo.Name;
if (IgnoreProperty(name))
{
continue;
}
PropertyBuilder propertyBuilder =
typeBuilder.DefineProperty(name, PropertyAttributes.None,
propertyInfo.PropertyType, null);

MethodBuilder getBuilder =
typeBuilder.DefineMethod("get_" + name, MethodAttributes.Public,
propertyInfo.PropertyType, Type.EmptyTypes);

ILGenerator getILG = getBuilder.GetILGenerator();
getILG.Emit(OpCodes.Ldarg_0);
getILG.Emit(OpCodes.Ldfld, wrappedObjectField);
getILG.Emit(OpCodes.Call, propertyInfo.GetGetMethod());
getILG.Emit(OpCodes.Ret);

propertyBuilder.SetGetMethod(getBuilder);
}

No comments: