Welcome to Ars-Informatica  

 
 
 
 
 
 

If you want to build a ship don't herd people together to collect wood and don't assign them tasks and work, but rather teach them to long for the endless immensity of the sea. (Antoine-Marie-Roger de Saint-Exupéry)

Emitting properties in .NET


If you're familiar with the Regex class (in the System.Text.RegularExpressions namespace) you may have already noticed that it, too, has the ability to compile your favorite regular expressions into a .NET assembly. In fact, the .NET Common Language Runtime (CLR) contains a whole namespace full of classes to help us build assemblies, define types, and emit their implementations, all at run time. These classes, which comprise the System.Reflection.Emit namespace, are known collectively as "Reflection.Emit." In this article I will present a convenient method to emit properties attached to classes/structures such that the Object created at run-time is suitable to be loaded into a PropertyGrid as SelectedObject. Consider we have a PropertyGridAttribute that extends Attribute in this way


    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, 
        AllowMultiple = false)]
    public class PropertyGridAttribute : Attribute
    {
        private String displayName;
        public String DisplayName { 
            get { return this.displayName; } 
            set { this.displayName = value; } 
        }

        private String description;
        public String Description { 
            get { return this.description; } 
            set { this.description = value; } 
        }
    }

In order to create a general mechanism to spill-out properties from a structure I will use generics.


    public abstract class HeaderBase<TStruct>
        where TStruct : struct
    {
        private TStruct _header;
        public TStruct Header { get { return this._header; } }
    ...
    }

This class will have a method called, say, ToPropertyPage that will return the run-time Object derived from TStruct enriched with the above properties.

The first thing to do is to create a new assembly by means of AssemblyBuilder. It makes incredibly easy to create assemblies in your code and add resources to it on the fly.


        public Object ToPropertyPage()
        {
            FieldInfo fiHeader = typeof(HeaderBase<TStruct>).GetField(
                "_header", 
                BindingFlags.Instance | BindingFlags.NonPublic
            );

            if (fiHeader == null)
            {
                return null;
            }

            AssemblyBuilder ab = Thread.GetDomain().DefineDynamicAssembly(
                new AssemblyName("PropertyPage"), AssemblyBuilderAccess.Run);

Then we create the dynamic module and a type (class) with the same name of the structure.


    ModuleBuilder modb = ab.DefineDynamicModule("PropertyPage");
    TypeBuilder tb = modb.DefineType(
        typeof(TStruct).Name, 
        TypeAttributes.Public | TypeAttributes.Class | 
            TypeAttributes.Sealed
    );

For each element of the structure we create the holding object and the accessors methods (get and set).


    foreach (FieldInfo fiField in fiHeader.FieldType.GetFields())
    {
        PropertyGridAttribute[] attributes = 
            (PropertyGridAttribute[])fiField.GetCustomAttributes(
                typeof(PropertyGridAttribute), 
                true
            );
        if (attributes.Length == 0) continue;

        PropertyGridAttribute attribute = attributes[0];

        FieldBuilder fb = tb.DefineField(
            fiField.Name,
            typeof(String),
            FieldAttributes.Public
        );

        PropertyBuilder pb = tb.DefineProperty(
            attribute.DisplayName,
            PropertyAttributes.HasDefault,
            fiField.FieldType,
            Type.EmptyTypes
        );

        Type[] cp = new Type[] { typeof(String), typeof(String) };
        ConstructorInfo ci = 
            typeof(PropertyGridAttribute).GetConstructor(cp);
        CustomAttributeBuilder cab = new CustomAttributeBuilder(
            ci,
            new Object[] { attribute.DisplayName, attribute.Description }
        );

        pb.SetCustomAttribute(cab);

        {
            MethodBuilder mb = tb.DefineMethod(
                String.Format("Get{0}", attribute.DisplayName),
                MethodAttributes.Public | MethodAttributes.SpecialName | 
                    MethodAttributes.HideBySig,
                typeof(String),
                Type.EmptyTypes
            );

            ILGenerator gen = mb.GetILGenerator();
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldfld, fb);
            gen.Emit(OpCodes.Ret);
            pb.SetGetMethod(mb);
        }

        {
            MethodBuilder mb = tb.DefineMethod(
                String.Format("Set{0}", attribute.DisplayName),
                MethodAttributes.Public | MethodAttributes.SpecialName | 
                    MethodAttributes.HideBySig,
                typeof(String),
                Type.EmptyTypes
            );

            ILGenerator gen = mb.GetILGenerator();
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Stfld, fb);
            gen.Emit(OpCodes.Ret);
            pb.SetSetMethod(mb);
        }
    }

The so-called setters and getters methods are generated at run-time using emit. Emit uses IL istructions. Every IL instruction consists of an opcode followed by zero or more operands. In general, it's helpful to think of the opcodes beginning with "Ld" as pushing items onto the stack, and the opcodes beginning with "St" as popping items off the stack. For a complete reference, refer to the Common Intermediate Language (CIL) specification.

The last operation to perform is to create an instance of the new type and populate it accordingly


    Object instance = Activator.CreateInstance(tb.CreateType());

    foreach (FieldInfo fiField in fiHeader.FieldType.GetFields())
    {
        String value = Convert.ToString(
            _header.GetType().GetField(fiField.Name).GetValue(_header));
        instance.GetType().GetField(fiField.Name).SetValue(
            instance, value);
    }

    return instance;

In order to use the code above follow these steps.

First create the structure with the properties


    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct MY_STRUCT
    {
        [PropertyGridAttribute("Version", "Software version")]
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string version;

        [PropertyGridAttribute("Author", "Software author")]
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string author;
    };

Then create the wrapper class around the structure to perform the trick:


    public class MyStruct : HeaderBase<MY_STRUCT>
    {
    }