Quickly generate and use Dynamic Class


This article aims at quickly show you how to create and use a dynamic type/class.

What do I call 'Dynamic Class'?

Occasionally I would like to add properties or methods to a class on the fly and generate a new Type and be able to instantiate it and make the rest of the application use it.

The new trend is to bind UI controls to classes that that contain data and present the data effortlessly. WPF and Silverlight use such binding extensively and by employing MVVM pattern the developer may create a very generic View (UI) then programmatically manipulate the data (Model) then bind the two and present the data ('effortlessly').

When do I use a 'Dynamic Class'?

Assume a query engine that in response to a query provides a table with 5 columns out of total of 20 existing columns - why 5? - Only because 20 column do not fit on a page. For example: A database of musical CD's, which present the Composer, Composition, Conductor, Orquestra and Soloist as the response-table. The database also includes the make, serial number, recording year, price and more.

The query itself returns all 20 columns (Model) but the code populates the Data-to-Present class (ViewModel) with the only 5 columns, then I bind it to the view and present it. The DataGrid- out of the box (WPF/Silverlight) - is capable to digest the bound class and to spit out a grid that shows a column for each public property in the bound class.

When the user queries for CD's that cost less than $20, it will be nice to show an additional column for the price, same if the query is about recording year, etc.

It will be nice if I can dynamically add properties to the Data-to-Present class, so when I bind it to the data grid view it will automatically generate the desired view for me – 'Dynamic Class'

How do I create Dynamic Class?

I actually create a dynamic type and instantiate it.

How do I create Dynamic Type?

The following code shows how to use Reflection in order to create a new type. Few things are worth noting here:

  • I create a type that is derived from an existing type since it will make it easier to refer to the new type later in the code.

  • Even though I do not use the new type's name I have to supply one that is unique.

  • Verify the property doesn't exist already.


The following method takes 2 names (new-type's name, new-property's name) and 2 types (new-property's type, existing-base type) and does all the heavy lifting needed in order to generate a (base) derived type with the requested property.

        public static Type CreateMyNewType(string newTypeName, string propertyName, Type propertyType, Type baseClassType)
        {
            // create a dynamic assembly and module
            AssemblyBuilder assemblyBldr =
            Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("tmpAssembly"), AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBldr = assemblyBldr.DefineDynamicModule("tmpModule");

            // create a new type builder
            TypeBuilder typeBldr = moduleBldr.DefineType(newTypeName, TypeAttributes.Public | TypeAttributes.Class, baseClassType);

            // Generate a private field for the property
            FieldBuilder fldBldr = typeBldr.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
            // Generate a public property
            PropertyBuilder prptyBldr =
                        typeBldr.DefineProperty(propertyName, PropertyAttributes.None, propertyType, new Type[] { propertyType });
            // The property set and property get methods need the following attributes:
            MethodAttributes GetSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig;
            // Define the "get" accessor method for newly created private field.
            MethodBuilder currGetPropMthdBldr =
                        typeBldr.DefineMethod("get_value", GetSetAttr, propertyType, null);

            // Intermediate Language stuff... as per MS
            ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
            currGetIL.Emit(OpCodes.Ldarg_0);
            currGetIL.Emit(OpCodes.Ldfld, fldBldr);
            currGetIL.Emit(OpCodes.Ret);

            // Define the "set" accessor method for the newly created private field.
            MethodBuilder currSetPropMthdBldr = typeBldr.DefineMethod("set_value", GetSetAttr, null, new Type[] { propertyType });

            // More Intermediate Language stuff...
            ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
            currSetIL.Emit(OpCodes.Ldarg_0);
            currSetIL.Emit(OpCodes.Ldarg_1);
            currSetIL.Emit(OpCodes.Stfld, fldBldr);
            currSetIL.Emit(OpCodes.Ret);
            // Assign the two methods created above to the PropertyBuilder's Set and Get
            prptyBldr.SetGetMethod(currGetPropMthdBldr);
            prptyBldr.SetSetMethod(currSetPropMthdBldr);
            // Generate (and deliver) my type
            return typeBldr.CreateType();
        }


How do I use the Dynamic Class?

One of the benefits of having this new dynamic type derived from an existing type is that I can use the existing type to hold the dynamic type or use the 'var' keyword to define the variable.

But doing the above will not allow me to utilize the new property: the compiler will complain since the compiler is unaware of the new type. So one thing I can do is make the compiler more agreeable by declaring the variable that holds the new type using the new keyword 'dynamic'. Doing so deferred the checking to run time, at which point the new type has the requested property:

        public static Type CreateMyNewType(string newTypeName, Dictionary dict, Type baseClassType)
        {
            bool noNewProperties = true;
            // create a dynamic assembly and module
            AssemblyBuilder assemblyBldr = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("tmpAssembly"), AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBldr = assemblyBldr.DefineDynamicModule("tmpModule");

            // create a new type builder
            TypeBuilder typeBldr = moduleBldr.DefineType(newTypeName, TypeAttributes.Public | TypeAttributes.Class, baseClassType);

            // Loop over the attributes that will be used as the properties names in my new type
            string propertyName = null;
            Type propertyType = null;
            var baseClassObj = Activator.CreateInstance(baseClassType);
            foreach (var word in dict)
            {
                propertyName = word.Key;
                propertyType = word.Value;
 
                //is it already in the base class?
                var src_pi = baseClassObj.GetType().GetProperty(propertyName);
                if (src_pi != null)
                {
                    continue;
                }

                // Generate a private field for the property
                FieldBuilder fldBldr = typeBldr.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
                // Generate a public property
                PropertyBuilder prptyBldr = typeBldr.DefineProperty(propertyName, PropertyAttributes.None, propertyType,
                                                                    new Type[] { propertyType });

                // The property set and property get methods need the following attributes:
                MethodAttributes GetSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig;
                // Define the "get" accessor method for newly created private field.
                MethodBuilder currGetPropMthdBldr = typeBldr.DefineMethod("get_value", GetSetAttr, propertyType, null);

                // Intermediate Language stuff... as per MS
                ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
                currGetIL.Emit(OpCodes.Ldarg_0);
                currGetIL.Emit(OpCodes.Ldfld, fldBldr);
                currGetIL.Emit(OpCodes.Ret);
 
                // Define the "set" accessor method for the newly created private field.
                MethodBuilder currSetPropMthdBldr = typeBldr.DefineMethod("set_value", GetSetAttr, null, new Type[] { propertyType });

                // More Intermediate Language stuff...
                ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
                currSetIL.Emit(OpCodes.Ldarg_0);
                currSetIL.Emit(OpCodes.Ldarg_1);
                currSetIL.Emit(OpCodes.Stfld, fldBldr);
                currSetIL.Emit(OpCodes.Ret);

                // Assign the two methods created above to the PropertyBuilder's Set and Get
                prptyBldr.SetGetMethod(currGetPropMthdBldr);
                prptyBldr.SetSetMethod(currSetPropMthdBldr);
                noNewProperties = false; //I added at least one property
            }
            if (noNewProperties == true)
            {
                return baseClassType; //deliver the base class
            }
            // Generate (and deliver) my type
            return typeBldr.CreateType();
        }

The following code illustrates how to use the above method and how to use the same dictionary to populate the new type/class.

protected Dictionary ExtendedColumnsDict = null;
       
        public void start(XElement dynamicQuery)
        {
            ExtendedColumnsDict = ExtractFromXelement(dynamicQuery);
        }

        protected override ClassicP CreateClassicP(dynamic src, Dictionary extendedColumnsDict)
        {
            //create the dynamic type & class based on the dictionary - hold the extended class in a variable of the base-type
            ClassicP classicEx = DynamicFactory.CreateClass(GetExtendedType(extendedColumnsDict));
            //fill in the base-type's properties
            classicEx.Composer = src.ComposerName;
            classicEx.Composition = src.CompositionName;
            classicEx.Orquestra = src.Orquestra;
            classicEx.Conductor = src.Conductor;
            classicEx.SubCategory = src.SubCategory;
            classicEx.Maker = src.Make;
            classicEx.Details = src;
            classicEx.Id = src.RecordingId;
            classicEx.CdBoxId = src.CD_BoxId;
            //fill in the dynamically created properties
            SetExtendedProperties(classicEx as dynamic, src, extendedColumnsDict);
            return classicEx;
        }
 
        //generic method that will extend dynamically the type T. keeping a global vraible that holds the dynamic type
        //makes usre i create type only once (the class on the other hand has to be instantiated for each data-row
        protected Type GetExtendedType(Dictionary extendedColumnsDict) where T : class
        {
            if (ExtendedType == null)
            {
                ExtendedType = DynamicFactory.ExtendTheType(ExtendedColumnsDict);
            }
            return ExtendedType;
        }

        //generic method that enumarets the dictionary and populate the dynamic class with values from the source
        //class that contains all the 20 columns.
        //there is an assumption here that the newly created properties have the same name as the original ones
        //in the source class.
        //the dynamic class (destination) is passed-in using the keyword dynamic in order defer the GetType()
        //operation until runtime when the dynamic type is available.
        protected void SetExtendedProperties(dynamic dest, T src, Dictionary extendedPropsDict)
        {
            foreach (var word in extendedPropsDict)
            {
                var src_pi = src.GetType().GetProperty(word.Key);
                var dest_pi = dest.GetType().GetProperty(word.Key) as PropertyInfo;
                var val = src_pi.GetValue(src, null);
                //format the data based on its type
                if (val is DateTime)
                {
                    dest_pi.SetValue(dest, ((DateTime) val).ToShortDateString(), null);
                }
                else if (val is decimal)
                {
                    dest_pi.SetValue(dest, ((decimal) val).ToString("C"), null);
                }
                else
                {
                    dest_pi.SetValue(dest, val, null);
                }
            }
        }

A word about the demo's source code

The Demo uses the free DataGrid from DevExpress. Microsoft's grid doesn't behave as expected with the dynamic class while Telerik and DevExpress controls handles the dynamic properties as expected.

On the other hand for some reason DevExpress control didn't respond as expected to the NotifyPropertyChanged event, therefore I needed to assign the DataContext as part of the submit button. Microsoft and Telerik controls both behaved properly to the event.
 


Similar Articles