Thursday, October 09, 2008

Generating Dynamic Types at Runtime Through Reflection in .NET

(I realize that I am terrible at blogging regularly).

This is an old sample I worked out a few months back. Upon further review, I could probably have done something with XSLT and deserialization, but it was a concious effort to use reflection to emit some code. You can skip the background if you want to get straight to code, ya lazy jerks ;-)

So, without further ado:

Business Problem: Custom data needs to be visible to the user in a grid. No customization of code, pure data configuration.

Assumptions: Use .NET WinForms stock controls. There is an existing business/entity object framework that should be altered as little as possible (ideal being no modifications at all). Object tree looks as such:
  • Person has a name.

  • Person has an address.

  • Person has business specific properties that are constant throughout all scenarios.

  • Person will have custom key/value pairs that are constructed from data at run time (therefore, we have a custom object we'll call "CustomEntity" that is basically an object holding key/value pairs).

If you want to slap this heirarchical structure on top of a DataGridView, that ain't gonna happen. I know there are a number of third-party controls that purport to do this (I love you too, Infragistics) but that's not possible in this scenario. So, if I can't build a fancy control easily, the alternate approach is to build a generic object (little g, but we'll get to that in a sec) to bind to a stock control.

I know I may want to do this again, so let's abstract this with some sort of factory we'll call "TypeFactory." In real-life I'll put a constraint on this class to make sure I can only do this with certain classes that have the custom collection driving this need . . .


//for demo/reference's sake
public interface ICustomEntityHolder
{
  //structure to hold custom data
  List <>  CustomEntityCollection{get; set;}
 
  //method to lookup matching entry and return value
  object GetCustomEntityValue(string key);
 
  //method to save or overwrite key/value pair
  void SetCustomEntityValue(string key, object value);
 
  //need some way to seed the new type's instance with data
  void SeedObjectData(object seed);
}
 
public class CustomEntity
{
  public string Key;
  public object Value;
}
 
public class Person : ICustomEntityHolder
{
  public string Name;
  public string Address;
  public List CustomEntityCollection;
}
public static Type CreateExtendedType <> (T baseType) where T : ICustomEntityHolder
{
string assemblyNameValue = typeof(T).Name + "ExtendedEmit";
  AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = assemblyNameValue;

  AssemblyBuilder ab = Thread.GetDomain().DefineDynamicAssembly(
    assemblyName, 
    AssemblyBuilderAccess.Run);

  ModuleBuilder moduleBuilder = ab.DefineDynamicModule(assemblyName.Name);
  //create a type that derives from base T
TypeBuilder typeBuilder =
moduleBuilder.DefineType(
assemblyName.Name,
TypeAttributes.Class TypeAttributes.Public,
typeof(T));
 
  //base T needs to have a constructor that takes a copy of itself
//TODO: best way to seed new object's data?

ConstructorBuilder constructorBuilder =
typeBuilder.DefineConstructor(
 MethodAttributes.Public, 
 CallingConventions.Standard, 
 new Type[0]);

  ILGenerator ilGen = constructorBuilder.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0); //this arg
ilGen.Emit(OpCodes.Call, typeof(T).GetConstructor(new Type[0]));
ilGen.Emit(OpCodes.Ret);
 
  //iterate through the collection and create a public property on the 
  //extended object that accesses the underlying collection
  foreach(CustomEntity entity in baseType.CustomEntityCollection
  {
FieldBuilder fb = typeBuilder.DefineField(
   "_" + entity.Key, 
   typeof(object), 
   FieldAttributes.Private);
     PropertyBuilder pb = typeBuilder.DefineProperty(
   entity.Key, 
   PropertyAttributes.HasDefault, 
   typeof(object), 
   Type.EmptyTypes);
     ILGenerator propILGen;
     //create a getter that calls the underlying custom entity getter
MethodBuilder propGet = typeBuilder.DefineMethod(
   "get" + entity.Key, 
       MethodAttributes.Public, 
       typeof(object), 
       Type.EmptyTypes);

propILGen = propGet.GetILGenerator();
     MethodInfo customGetter = typeof(T).GetMethod(
   "GetCustomEntityValue", 
   new Type[] { typeof(string) });

propILGen.Emit(OpCodes.Ldarg_0);
propILGen.Emit(OpCodes.Ldstr, entity.Key);
propILGen.Emit(OpCodes.Call, customGetter);
propILGen.Emit(OpCodes.Ret);
     pb.SetGetMethod(propGet);

//create a setter that calls the underlying custom entity setter
     MethodBuilder propSet = typeBuilder.DefineMethod(
      "set" + entity.Key, 
        MethodAttributes.Public, 
        null, 
        new Type[] { typeof(object) });

propILGen = propSet.GetILGenerator();

     MethodInfo customSetter = typeof(T).GetMethod(
      "SetCustomEntityValue",
new Type[] { typeof(string), typeof(object) });

propILGen.Emit(OpCodes.Ldarg_0);
propILGen.Emit(OpCodes.Ldstr, entity.Key);
propILGen.Emit(OpCodes.Ldarg_1);
propILGen.Emit(OpCodes.Call, customSetter);
propILGen.Emit(OpCodes.Ret);
     pb.SetSetMethod(propSet);
}
   Type extendedType = typeBuilder.CreateType();
return extendedType;
}
So, an example of typical usage would be as such:

Person person = new Person();
Type extendedPersonType = TypeFactory.CreateExtendedType(person);
object extendedPerson = Activator.CreateInstance(extendedPersonType);
//yes, you could do this through the ICustomEntityHolder interface
MethodInfo seedDataMethod =
extendedPersonType.GetMethod("SeedObjectData");
if (seedDataMethod != null)
{seedDataMethod.Invoke(extendedPerson, new object[] { person});}

As a result you get an instance of a new type, preserving the existing properties, but with new properties representing each of the custom entity objects in that collection. You could take this object, or a collection thereof, and bind to a DataGridView. Overworked? Perhaps. Cool? Definitely.

No comments: