Saturday, March 14, 2009

Power of Reflection Emit

Some time ago I came across a situation where the end user of my application was willing to add new types at his own. Obviously I was not having any other solution rather using Reflection Emit.

It was great creating new types, dynamic methods at run time.

So as I did earlier similarly I will produce some code (to create new type (compiled class) with some properties and methods) out here with a little description.

But before working with Emit you should look on the MSIL Disassembler to know how the things looks alike after compilation and before JIT compiler processing.

------------------------------ Creating A Whole New Type ------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using DataFramework;
using System.Data;

namespace Generator
{
public class ClassGenerator
{
public ClassGenerator()
{

}

public void GenerateClass(string assemblyName, string className, List fieldWithType)
{
AssemblyName an = new AssemblyName(assemblyName);
AppDomain ad = AppDomain.CurrentDomain; // Use same app domain otherwise to use
// the generated classes you need to use
// remoting like - object handle unwrap
AssemblyBuilder ab = ad.DefineDynamicAssembly(an, AssemblyBuilderAccess.RunAndSave, AppDomain.CurrentDomain.BaseDirectory);

ModuleBuilder mb = ab.DefineDynamicModule(an.Name, "CustomClasses.dll");

TypeBuilder tb = mb.DefineType(className, TypeAttributes.Class | TypeAttributes.Public);

ConstructorInfo ci = tb.DefineDefaultConstructor(MethodAttributes.Public);

foreach (FieldStructure fieldName in fieldWithType)
{
Type type = GetType(fieldName.type);// Type.GetType(fieldWithType[fieldName], false, true);

FieldBuilder fb = tb.DefineField(fieldName.name, type, FieldAttributes.Private);

CreateProperty(tb, type, fb, fieldName.name);
}



Type generatedType = tb.CreateType();
ab.Save("CustomClasses." + className + ".dll");
}

#region Create Property for field
private void CreateProperty(TypeBuilder tb, Type fieldType, FieldBuilder fb, string key)
{
PropertyBuilder pb = tb.DefineProperty(SystemConstants.GetPropertyName(key), System.Reflection.PropertyAttributes.HasDefault, fieldType, Type.EmptyTypes);

MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

MethodBuilder mbGet = tb.DefineMethod(SystemConstants.getPrefix + key, getSetAttr, fieldType, Type.EmptyTypes);

ILGenerator getIL = mbGet.GetILGenerator();
// For an instance property, argument zero is the instance. Load the
// instance, then load the private field and return, leaving the
// field value on the stack.
getIL.Emit(OpCodes.Ldarg_0);
getIL.Emit(OpCodes.Ldfld, fb);
getIL.Emit(OpCodes.Ret);

MethodBuilder mbSet = tb.DefineMethod(SystemConstants.setPrefix + key, getSetAttr, null, new Type[] { fieldType });

ILGenerator setIL = mbSet.GetILGenerator();
// Load the instance and then the numeric argument, then store the
// argument in the field.
setIL.Emit(OpCodes.Ldarg_0);
setIL.Emit(OpCodes.Ldarg_1);
setIL.Emit(OpCodes.Stfld, fb);
setIL.Emit(OpCodes.Ret);

// Last, map the "get" and "set" accessor methods to the
// PropertyBuilder. The property is now complete.
pb.SetGetMethod(mbGet);
pb.SetSetMethod(mbSet);
}
#endregion

#region Get type based on string representation
private Type GetType(string typeName)
{
switch (typeName.ToLower())
{
case "string":
return typeof(String);
case "int32":
return typeof(Int32);
case "long":
return typeof(long);
case "float":
return typeof(float);
case "decimal":
return typeof(decimal);
default:
return typeof(object);
}
}
#endregion
}
}



----------------------- Helper Classes (SystemConstants)-----------------------------------
using System;
using System.Collections.Generic;
using System.Text;

namespace Generator
{
public class SystemConstants
{
public const string getPrefix = "get_";
public const string setPrefix = "set_";
public const string variablePrefix = "m_";
public const string idVariableForClasses = "ID";
public const string idVariableType = "int32";

private static bool IsFieldDicContainsId(Dictionary fieldWithType)
{
foreach (string fieldName in fieldWithType.Keys)
{
if (fieldName.ToUpper() == idVariableForClasses)
{
return true;
}
}
return false;
}

public static Dictionary GetFinalDictionary(Dictionary fieldWithType)
{
if (IsFieldDicContainsId(fieldWithType))
{
if (fieldWithType.ContainsKey(idVariableForClasses))
{
fieldWithType.Remove(idVariableForClasses);
}
}

return fieldWithType;
}

internal static string GetPropertyName(string key)
{
// Implement Logic to create property like m_name ==> Name right now its NAME
return key.ToUpper();
}
}
}

----------------------- Helper Structure (FieldStructure)-----------------------------------

using System;
using System.Collections.Generic;
using System.Text;

namespace Generator
{
public struct FieldStructure
{
public string name;
public string type;
public bool isIdentity;
public bool isReference;
public string referenceClassName;
public string referenceColumnName;

}
}



--------------------------------- Code to be called from UI ------------------------------------

ClassGenerator cg = new ClassGenerator();

FieldStructure fStructA = new FieldStructure();
fStructA.name = "name";
fStructA.type = "string";
fStructA.isIdentity = false;

FieldStructure fStructB = new FieldStructure();
fStructB.name = "id";
fStructB.type = "int32";
fStructB.isIdentity = true;

List allFields = new List();
allFields.Add(fStructA);
allFields.Add(fStructB);

cg.GenerateClass("test", "Test", allFields);
}


Thats it. Try to use it, a .dll file will be saved in your application root directory, but beware if you are using web application and you intended to save any .dll file in the bin folder the application domain will reload and all your sessions will be lost as one of the application domain reloading is any change in the bin directory is.

For more reading you can look at this msdn link.

Let me know for your queries.
Shashank.... enjoy coding ....