Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Quickly Generate and Use Dynamic Class

0.00/5 (No votes)
15 Sep 2010 1  
Quickly show you how to create and use a dynamic type/class

Introduction

This article aims at quickly showing 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 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 columns do not fit on a page. For example: A database of musical CDs, which present the Composer, Composition, Conductor, Orchestra and Solist 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 only the 5 columns, then I bind it to the view and present it. The DataGrid – out of the box (WPF/Silverlight) - is capable of digesting 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 CDs 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 Microsoft
    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:

Class NotDynamic
{
    public string Composer {get; set;}
    public string Composition {get; set;}
}
Type  dynamicType = CreateMyNewType(“newTypeName”, 
	“Cost”, typeof(decimal), typeof(NotDynamic));
//use this variable for existing properties
NotDynamic newClassBase =  Activator.CreateInstance(dynamicType);
newClass.Composer = “Beethoven”;
//use this variable for dynamically created properties.
dynamic newClass = newClassBase;
newClass.Cost = 19.50;

How Do I Make It Better?

To make it more flexible, I needed more than one property and to also be able to dynamically populate the dynamically generated property. Another improvement may be to create some of the methods generic.

To do that, I introduced a dictionary that contains a key –value of string-Type (property-name, property type). This dictionary is populated by the query engine (as the user selects the criteria). The same dictionary is passed to the code that populates the dynamic class so it will know which properties to populate. In order for this scheme to work, a certain naming convention must be kept.

public static Type CreateMyNewType
    (string newTypeName, Dictionary<string> 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 Microsoft
                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<string> ExtendedColumnsDict = null;
        
        public void start(XElement dynamicQuery)
        {
            ExtendedColumnsDict = ExtractFromXelement(dynamicQuery);
        }
        
        protected override ClassicP CreateClassicP
		(dynamic src, Dictionary<string> 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
			<classicp>(GetExtendedType<classicp>(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 variable that holds the dynamic type
        //makes sure i create type only once 
        //(the class on the other hand has to be instantiated for each data-row
        protected Type GetExtendedType<t>
	(Dictionary<string> extendedColumnsDict) where T : class
        {
            if (ExtendedType == null)
            {
                ExtendedType = DynamicFactory.ExtendTheType<t>(ExtendedColumnsDict);
            }
            return ExtendedType;
        }
        
        //generic method that enumerates 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
	<t>(dynamic dest, T src, Dictionary<string,> 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.

History

  • 9-12-2010 First draft

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here