Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

Creating classes at runtime

, 15 Jun 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Creating classes 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 initialise 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:

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:

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:

/// <span class="code-SummaryComment"><summary>
</span>/// Creates an instance of the adapter.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="page">The page data.</param>
</span>/// <span class="code-SummaryComment"><returns>An adapter.</returns>
</span>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 });
}

/// <span class="code-SummaryComment"><summary>
</span>/// Creates the adapter class.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="page">The page data.</param>
</span>/// <span class="code-SummaryComment"><returns>An adapter type.</returns>
</span>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();
}

/// <span class="code-SummaryComment"><summary>
</span>/// Generates a constructor that takes one argument (list of controls) and assigned it to the 
/// controls field.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="adapterTypeBuild">The adapter type builder.</param>
</span>/// <span class="code-SummaryComment"><param name="ctrlsFieldBuild">The controls field builder.</param>
</span>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);
}

/// <span class="code-SummaryComment"><summary>
</span>/// Generates a property with a getter and setter which can be bound to the corresponding
/// control when it is created.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="adapterTypeBuild">The adapter type builder.</param>
</span>/// <span class="code-SummaryComment"><param name="ctrlsFieldBuild">The controls field builder.</param>
</span>/// <span class="code-SummaryComment"><param name="controlIndex">
</span>/// The index of the current control in the list of controls.<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><param name="control">
</span>/// The control the property is being created for.<span class="code-SummaryComment"></param>
</span>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));
}

/// <span class="code-SummaryComment"><summary>
</span>/// Gets the type of the property, based on the type of control.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="control">The control.</param>
</span>/// <span class="code-SummaryComment"><returns>The property type.</returns>
</span>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;
}

/// <span class="code-SummaryComment"><summary>
</span>/// Creates the "get" accessor method for a property.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="adapterTypeBuild">
</span>/// The adapter type builder.<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><param name="ctrlsFieldBuild">
</span>/// The controls field builder.<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><param name="controlIndex">
</span>/// The index of the current control in the list of controls.<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><param name="control">
</span>/// The control the property is being created for.<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><param name="getSetAttr">The method attributes.</param>
</span>/// <span class="code-SummaryComment"><param name="propertyType">
</span>/// The property type (return type of the getter).<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><returns>The get method method builder.</returns>
</span>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;
}

/// <span class="code-SummaryComment"><summary>
</span>/// Creates the "set" accessor method for a property.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="adapterTypeBuild">
</span>/// The adapter type builder.<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><param name="ctrlsFieldBuild">
</span>/// The controls field builder.<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><param name="controlIndex">
</span>/// The index of the current control in the list of controls.<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><param name="control">
</span>/// The control the property is being created for.<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><param name="getSetAttr">The method attributes.</param>
</span>/// <span class="code-SummaryComment"><param name="propertyType">
</span>/// Property type (the type of the setters parameter).<span class="code-SummaryComment"></param>
</span>/// <span class="code-SummaryComment"><returns>The set method method builder.</returns>
</span>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;
}

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

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

/// <span class="code-SummaryComment"><summary>
</span>/// Gets the value property based on the type of the control 
/// (StringValue, BoolValue etc).
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="control">The control.</param>
</span>/// <span class="code-SummaryComment"><returns>The name of the property.</returns>
</span>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)

Share

About the Author

S1mm0t

United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411022.1 | Last Updated 15 Jun 2009
Article Copyright 2009 by S1mm0t
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid