Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Creating Classes at Runtime

5.00/5 (2 votes)
15 Jun 2009CPOL2 min read 19.2K  
How to create a class at runtime

I've been working on an application that has a number of forms that need to be laid out based on configuration data in the database. This enables the application to meet the requirements of many different customers. Laying out the forms based on the configuration data is relatively trivial - it is just a matter of writing the code that normally ends up in the .designer file. However, because there could be any number of controls on the form, binding an object to them is a little more tricky. I approached this problem by creating a class that describes a control that will added to the form together with a boolean, integer and string value field that is used to initialize the control and capture the users input based on the type of control. I had a list of these objects representing all of the controls that needed to be rendered on the form, to bind the controls to these objects, I needed an adapter which I created at runtime.

Effectively, the adapter was pretty straight forward. It required a constructor which would take a list of the control classes described above. Then it would have a series of properties which would get and set values in the list, for example:

C#
public Adapter
{
    private IList<POWPageControl> _controls;

    public Adapter(IList<POWPageControl> controls)
    {
        _controls = controls;
    }

    public Property1
    {
        get { return _controls[0].StringValue; }
        set { _controls[0].StringValue = value; }
    }

    public Property2
    {
        get { return _controls[1].BoolValue; }
        set { _controls[1].BoolValue = value; }
    }
}

In order to prevent a memory leak, I added the following code to the views constructor - it checks that I haven't already created the assembly that will contain the dynamic code, before I create it:

C#
private const string ADAPTER_ASSEMBLY_NM = "DynamicPOWAdapterAssembly";
private const string ADAPTER_MODULE_NM = "DynamicPOWAdapterModule";

private AssemblyBuilder _adapterAssemBuild;

public POWEditView()
{
    AppDomain adapterDomain = AppDomain.CurrentDomain;

    AssemblyName adapterAssemNm = new AssemblyName(ADAPTER_ASSEMBLY_NM);

    foreach (Assembly assembly in adapterDomain.GetAssemblies())
    {
        AssemblyName assemNm = assembly.GetName();

        // This isn't perfect, but should be good enough 
        // to determine if we have already created
        // the dynamic assembly (this is to prevent a memory leak)
        if (assemNm.FullName.StartsWith(adapterAssemNm.FullName) 
        	&& assembly is AssemblyBuilder)
        {
            _adapterAssemBuild = assembly as AssemblyBuilder;
            break;
        }
    }

    if (_adapterAssemBuild == null)
    _adapterAssemBuild = adapterDomain.DefineDynamicAssembly
    	(adapterAssemNm, AssemblyBuilderAccess.Run);

    InitializeComponent();
}

Once you have an assembly for the dynamic class, there are then a series of other steps to go through - create a module, create the type, add the properties to the type and then create the get and set methods for the properties:

C#
/// <summary>
/// Creates an instance of the adapter.
/// </summary>
/// <param name="page">The page data.</param>
/// <returns>An adapter.</returns>
private object CreateAdapter(POWPage page)
{
    // This is to prevent a memory leak. Assemblies and therefore types can't be unloaded
    // until the app domain they are loaded in is unloaded. What this does mean is that
    // if you updated the structure of the page by adding extra controls, 
    // an error would occur
    // since a corresponding property wouldn't exist in the dynamically created adapter. 
    // If you really wanted to get round this, then you could assign the POWPage a new guid.
    Type adapterType = _adapterAssemBuild.GetType(page.AdapterTypeName);

    if (adapterType == null)
        adapterType = CreateAdapterType(page);

    return Activator.CreateInstance(adapterType, new object[] { page.Controls });
}

/// <summary>
/// Creates the adapter class.
/// </summary>
/// <param name="page">The page data.</param>
/// <returns>An adapter type.</returns>
private Type CreateAdapterType(POWPage page)
{
    // Use of guid is to ensure module name is uniqe 
    // (each type created will have its own module)
    ModuleBuilder adapterModBuild = 
	_adapterAssemBuild.DefineDynamicModule(ADAPTER_MODULE_NM + page.Guid);

    TypeBuilder adapterTypeBuild = 
    adapterModBuild.DefineType(page.AdapterTypeName, TypeAttributes.Public);

    // The adapter will have one field - the list of controls
    FieldBuilder ctrlsFieldBuild =
    adapterTypeBuild.DefineField(
                   page.AdapterCtrlsFieldNm, 
                   typeof(IList<POWPageControl>), 
                   FieldAttributes.Private);

    GenerateConstructor(adapterTypeBuild, ctrlsFieldBuild);

    for (int i = 0; i < page.Controls.Count; i++)
    {
        POWPageControl control = page.Controls[i];

        // Labels aren't expected to be data bound
        if (control.ControlType != POWPageControlTypeEnum.Label)
            GenerateProperty(adapterTypeBuild, ctrlsFieldBuild, i, control);
    }
    return adapterTypeBuild.CreateType();
}

/// <summary>
/// Generates a constructor that takes one argument (list of controls) and assigned it to the 
/// controls field.
/// </summary>
/// <param name="adapterTypeBuild">The adapter type builder.</param>
/// <param name="ctrlsFieldBuild">The controls field builder.</param>
private void GenerateConstructor(TypeBuilder adapterTypeBuild, 
                                        FieldBuilder ctrlsFieldBuild)
{
    // Base class and base constructor
    ConstructorInfo baseCtor = typeof(object).GetConstructor(new Type[] { });

    // The constructor for the adapter will take one argument - the list of controls
    ConstructorBuilder adapterCtor =
               adapterTypeBuild.DefineConstructor(
                   MethodAttributes.Public,
                   CallingConventions.Standard,
                   new Type[] { ctrlsFieldBuild.FieldType });

    // Generate the constructor code - invoke the base constructor and the assign the
    // first argument to the controls field.
    ILGenerator ctorIL = adapterCtor.GetILGenerator();
    ctorIL.Emit(OpCodes.Ldarg_0);
    ctorIL.Emit(OpCodes.Call, baseCtor);
    ctorIL.Emit(OpCodes.Ldarg_0);
    ctorIL.Emit(OpCodes.Ldarg_1);
    ctorIL.Emit(OpCodes.Stfld, ctrlsFieldBuild);
    ctorIL.Emit(OpCodes.Ret);
}

/// <summary>
/// Generates a property with a getter and setter which can be bound to the corresponding
/// control when it is created.
/// </summary>
/// <param name="adapterTypeBuild">The adapter type builder.</param>
/// <param name="ctrlsFieldBuild">The controls field builder.</param>
/// <param name="controlIndex">
/// The index of the current control in the list of controls.</param>
/// <param name="control">
/// The control the property is being created for.</param>
private void GenerateProperty(TypeBuilder adapterTypeBuild, 
                                     FieldBuilder ctrlsFieldBuild, 
                                     int controlIndex,
                                     POWPageControl control)
{
    Type propertyType = GetPropertyType(control);

    PropertyBuilder propertyBuild =
    adapterTypeBuild.DefineProperty(
                   control.ValuePropertyName,
                   PropertyAttributes.None,
                   propertyType,
                   null);

    // The property get and set methods require a special set of attributes
    MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig;

    propertyBuild.SetGetMethod(CreatePropertyGet(adapterTypeBuild, 
    ctrlsFieldBuild, controlIndex, control, getSetAttr, propertyType));
    propertyBuild.SetSetMethod(CreatePropertySet(adapterTypeBuild, 
    ctrlsFieldBuild, controlIndex, control, getSetAttr, propertyType));
}

/// <summary>
/// Gets the type of the property, based on the type of control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The property type.</returns>
private Type GetPropertyType(POWPageControl control)
{
    Type type = null;
    
    switch (control.ControlType)
    {
        case POWPageControlTypeEnum.ComboBox:
        case POWPageControlTypeEnum.RadioButton:
            type = typeof(long?);
            break;
            
        case POWPageControlTypeEnum.TextBox:
            type = typeof(string);
            break;
             
         case POWPageControlTypeEnum.CheckBox:
            type = typeof(bool?);
            break;
            
        default:
            throw new NotSupportedException("Unexpected control type");
    }
    return type;
}

/// <summary>
/// Creates the "get" accessor method for a property.
/// </summary>
/// <param name="adapterTypeBuild">
/// The adapter type builder.</param>
/// <param name="ctrlsFieldBuild">
/// The controls field builder.</param>
/// <param name="controlIndex">
/// The index of the current control in the list of controls.</param>
/// <param name="control">
/// The control the property is being created for.</param>
/// <param name="getSetAttr">The method attributes.</param>
/// <param name="propertyType">
/// The property type (return type of the getter).</param>
/// <returns>The get method builder.</returns>
private MethodBuilder CreatePropertyGet(TypeBuilder adapterTypeBuild, 
                                        FieldBuilder ctrlsFieldBuild,
                                        int controlIndex,
                                        POWPageControl control, 
                                        MethodAttributes getSetAttr,
                                        Type propertyType)
{
    MethodBuilder getMethBuild =
        adapterTypeBuild.DefineMethod(
            "get_" + control.ValuePropertyName,
            getSetAttr,
            propertyType,
            null);
            
    // Implement the get method
    ILGenerator getMethIL = getMethBuild.GetILGenerator();
    
    System.Reflection.Emit.Label label = getMethIL.DefineLabel();
    getMethIL.DeclareLocal(propertyType);
    
    getMethIL.Emit(OpCodes.Nop);
    getMethIL.Emit(OpCodes.Ldarg_0);
    getMethIL.Emit(OpCodes.Ldfld, ctrlsFieldBuild);
    getMethIL.Emit(OpCodes.Ldc_I4, controlIndex);
    getMethIL.Emit(OpCodes.Callvirt, 
    ctrlsFieldBuild.FieldType.GetMethod("get_Item"));
    getMethIL.Emit(OpCodes.Callvirt, 
    typeof(POWPageControl).GetMethod(GetGetValueProperty(control)));
    getMethIL.Emit(OpCodes.Stloc_0);
    getMethIL.Emit(OpCodes.Br_S, label);
    getMethIL.MarkLabel(label);
    getMethIL.Emit(OpCodes.Ldloc_0);
    getMethIL.Emit(OpCodes.Ret);
    
    return getMethBuild;
}

/// <summary>
/// Creates the "set" accessor method for a property.
/// </summary>
/// <param name="adapterTypeBuild">
/// The adapter type builder.</param>
/// <param name="ctrlsFieldBuild">
/// The controls field builder.</param>
/// <param name="controlIndex">
/// The index of the current control in the list of controls.</param>
/// <param name="control">
/// The control the property is being created for.</param>
/// <param name="getSetAttr">The method attributes.</param>
/// <param name="propertyType">
/// Property type (the type of the setters parameter).</param>
/// <returns>The set method builder.</returns>
private MethodBuilder CreatePropertySet(TypeBuilder adapterTypeBuild,
                                        FieldBuilder ctrlsFieldBuild,
                                        int controlIndex,
                                        POWPageControl control,
                                        MethodAttributes getSetAttr,
                                        Type propertyType)
{
    MethodBuilder setMethBuild =
        adapterTypeBuild.DefineMethod(
            "set_" + control.ValuePropertyName,
            getSetAttr,
            null,
            new Type[] { propertyType });
            
    ILGenerator setMethIL = setMethBuild.GetILGenerator();
    
    setMethIL.Emit(OpCodes.Nop);
    setMethIL.Emit(OpCodes.Ldarg_0);
    setMethIL.Emit(OpCodes.Ldfld, ctrlsFieldBuild);
    setMethIL.Emit(OpCodes.Ldc_I4, controlIndex);
    setMethIL.Emit(OpCodes.Callvirt, 
    	ctrlsFieldBuild.FieldType.GetMethod("get_Item"));
    setMethIL.Emit(OpCodes.Ldarg_1);
    setMethIL.Emit(OpCodes.Callvirt, 
    	typeof(POWPageControl).GetMethod(GetSetValueProperty(control)));
    setMethIL.Emit(OpCodes.Nop);
    setMethIL.Emit(OpCodes.Ret);
    
    return setMethBuild;
}

/// <summary>
/// Gets the value properties getter based on the type of the control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The name of the getter.</returns>
private string GetGetValueProperty(POWPageControl control)
{
    return "get_" + GetValueProperty(control);
}

/// <summary>
/// Gets the value properties setter based on the type of the control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The name of the setter.</returns>
private string GetSetValueProperty(POWPageControl control)
{
    return "set_" + GetValueProperty(control);
}

/// <summary>
/// Gets the value property based on the type of the control 
/// (StringValue, BoolValue etc).
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The name of the property.</returns>
private string GetValueProperty(POWPageControl control)
{
    string property = null;
    
    switch (control.ControlType)
    {
        case POWPageControlTypeEnum.ComboBox:
        case POWPageControlTypeEnum.RadioButton:
            property = "IntValue";
            break;
            
        case POWPageControlTypeEnum.TextBox:
            property = "StringValue";
            break;
            
        case POWPageControlTypeEnum.CheckBox:
            property = "BoolValue";
            break;
            
        default:
            throw new NotSupportedException("Unexpected control type");
    }
    return property;
}

The one thing that proved invaluable when creating this code was ildasm.exe. For example, I kept on getting a ProgramException when I ran my code and couldn't see what was wrong with my code. Knocking together the example adapter above and running it through ildasm soon showed me that the problem was because I was usingLdind_I4 instead of Ldc_I4.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)