Thursday, July 5, 2012

How to create a class with properties at run time

We might come across scenarios where we have to add properties to a class very often that might involve changing code. Hence we might consider the option of creating such classes dynamically at runtime and adding properties to it to use them.

The way we would create a class dynamically at run time is using Reflection.Emit namespace in the .NET framework along with ILGenerator.

First we create a class that records on what properties would be needed in a class as shown below:


public class DynamicLibraryProperties
{
       public string PropName { get; set; }
       public Type PropType { get; set; }

       /// The default value for any property is specified as string Ex: For integer is specified as "0", for single specified as "-999"
       public string DefaultValue { get; set; }
}

Now the below method shows on how we can create the class dynamically


public static void GenerateLegacyStructureObject(string libraryName, 
                                       string className, 
                                       List<DynamicLibraryProperties> properties)
{
       ILGenerator ilgen = default(ILGenerator);
       string library = string.Concat(libraryName, ".dll");
       AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                                       new AssemblyName(libraryName), 
                                       AssemblyBuilderAccess.RunAndSave);
       ModuleBuilder modBuilder = asmBuilder.DefineDynamicModule(libraryName, library);
       TypeBuilder legacyBuilder = modBuilder.DefineType(
                                   string.Concat(libraryName, ".", className), 
                                   TypeAttributes.Class | TypeAttributes.Public);
 
       //Field Builder - Based on number of months add so many fields
       foreach (DynamicLibraryProperties p in properties) 
       {
             FieldBuilder field = legacyBuilder.DefineField(
                                      string.Concat("_", p.PropName), 
                                      p.PropType, 
                                      FieldAttributes.Private);
             PropertyBuilder nameProp = legacyBuilder.DefineProperty(p.PropName, 
                                              PropertyAttributes.HasDefault, 
                                              p.PropType, null);
 
             Type[] types = new Type[] { p.PropType };
             dynamic typeConvertor = TypeDescriptor.GetConverter(p.PropType);
             dynamic defaultValue = typeConvertor.ConvertFromString(p.DefaultValue);
             ConstructorInfo ctor = typeof(DefaultValueAttribute).GetConstructor(types);
             CustomAttributeBuilder customAttrib = new CustomAttributeBuilder(
                                                        ctor, 
                                                        new object[] { defaultValue });
             nameProp.SetCustomAttribute(customAttrib);
 
             MethodAttributes getAttr = MethodAttributes.Public | 
                                          MethodAttributes.HideBySig | 
                                          MethodAttributes.SpecialName;
             MethodBuilder getNameBuilder = legacyBuilder.DefineMethod(
                                             string.Concat("get_", p.PropName),
                                             getAttr,
                                             p.PropType, Type.EmptyTypes);
             ilgen = getNameBuilder.GetILGenerator();
             ilgen.Emit(OpCodes.Ldarg_0);
             ilgen.Emit(OpCodes.Ldfld, field);
             ilgen.Emit(OpCodes.Ret);
 
             MethodBuilder setNameBuilder = legacyBuilder.DefineMethod(
                                               string.Concat("set_", p.PropName),
                                               getAttr, null
                                               new Type[] { p.PropType });
             ilgen = setNameBuilder.GetILGenerator();
             ilgen.Emit(OpCodes.Ldarg_0);
             ilgen.Emit(OpCodes.Ldarg_1);
             ilgen.Emit(OpCodes.Stfld, field);
             ilgen.Emit(OpCodes.Ret);
 
             nameProp.SetGetMethod(getNameBuilder);
             nameProp.SetSetMethod(setNameBuilder);
       }
 
       Type objType = Type.GetType("System.Object");
       ConstructorInfo objCtor = objType.GetConstructor(Type.EmptyTypes);
 
       ilgen.Emit(OpCodes.Ldarg_0);
       ilgen.Emit(OpCodes.Call, objCtor);
 
       ilgen.Emit(OpCodes.Ret);
 
       legacyBuilder.CreateType();
 
       asmBuilder.Save(library);
}

Now we use the above object in a List, Dictionary as shown in the below code:


List<DynamicLibProperties> props = new List<DynamicLibProperties>();
props.Add(new DynamicLibProperties {
                                         PropName = "201203",
                                         PropType = typeof(float)
                                   });
props.Add(new DynamicLibProperties {
                                         PropName = "201204",
                                         PropType = typeof(float)
                                   });
GenerateDynamicClass("DynamicLibrary1", "Legacy", props);
dynamic assemblyFileName = typeof(Form1).Assembly.CodeBase.Replace("file:///", "");
dynamic assemblyInfo = new FileInfo(assemblyFileName);
 
Assembly asm = Assembly.LoadFile(string.Concat(assemblyInfo.Directory,
                                         "\\DynamicLibrary1.dll"));
Type legacyType = asm.GetType("DynamicLibrary1.Legacy");
 
//Creating instance of the dynamically created class 
dynamic legacyObj = Activator.CreateInstance(legacyType);
 
//Creating a dictionary of integer, type of object created dynamically
dynamic genericDictionay = typeof(Dictionary<, >);
List<Type> p = new List<Type>();
p.Add(typeof(int));
p.Add(legacyType);
dynamic specificDic = genericDictionay.MakeGenericType(p.ToArray());
dynamic dicOfLegacyStructure = Activator.CreateInstance(specificDic);
dicOfLegacyStructure.Add(1, legacyObj);
 
//We set the value of a property as shown below
dynamic legObj = dicOfLegacyStructure(1);
if (legObj != null) {
PropertyInfo propInfo = legObj.GetType().GetProperty("201203");
propInfo.SetValue(legObj, 1020, null);
}

No comments: