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)
{
AssemblyBuilder assemblyBldr =
Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("tmpAssembly"),
AssemblyBuilderAccess.Run);
ModuleBuilder moduleBldr = assemblyBldr.DefineDynamicModule("tmpModule");
TypeBuilder typeBldr = moduleBldr.DefineType
(newTypeName, TypeAttributes.Public | TypeAttributes.Class, baseClassType);
FieldBuilder fldBldr = typeBldr.DefineField
("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder prptyBldr =
typeBldr.DefineProperty(propertyName,
PropertyAttributes.None, propertyType, new Type[] { propertyType });
MethodAttributes GetSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig;
MethodBuilder currGetPropMthdBldr =
typeBldr.DefineMethod("get_value", GetSetAttr, propertyType, null);
ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, fldBldr);
currGetIL.Emit(OpCodes.Ret);
MethodBuilder currSetPropMthdBldr = typeBldr.DefineMethod
("set_value", GetSetAttr, null, new Type[] { propertyType });
ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, fldBldr);
currSetIL.Emit(OpCodes.Ret);
prptyBldr.SetGetMethod(currGetPropMthdBldr);
prptyBldr.SetSetMethod(currSetPropMthdBldr);
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));
NotDynamic newClassBase = Activator.CreateInstance(dynamicType);
newClass.Composer = “Beethoven”;
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;
AssemblyBuilder assemblyBldr = Thread.GetDomain().DefineDynamicAssembly
(new AssemblyName("tmpAssembly"), AssemblyBuilderAccess.Run);
ModuleBuilder moduleBldr = assemblyBldr.DefineDynamicModule("tmpModule");
TypeBuilder typeBldr = moduleBldr.DefineType
(newTypeName, TypeAttributes.Public | TypeAttributes.Class, baseClassType);
string propertyName = null;
Type propertyType = null;
var baseClassObj = Activator.CreateInstance(baseClassType);
foreach (var word in dict)
{
propertyName = word.Key;
propertyType = word.Value;
var src_pi = baseClassObj.GetType().GetProperty(propertyName);
if (src_pi != null)
{
continue;
}
FieldBuilder fldBldr = typeBldr.DefineField
("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder prptyBldr = typeBldr.DefineProperty
(propertyName, PropertyAttributes.None, propertyType,
new Type[] {propertyType});
MethodAttributes GetSetAttr = MethodAttributes.Public |
MethodAttributes.HideBySig;
MethodBuilder currGetPropMthdBldr = typeBldr.DefineMethod
("get_value", GetSetAttr, propertyType, null);
ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, fldBldr);
currGetIL.Emit(OpCodes.Ret);
MethodBuilder currSetPropMthdBldr = typeBldr.DefineMethod
("set_value", GetSetAttr, null, new Type[] {propertyType});
ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, fldBldr);
currSetIL.Emit(OpCodes.Ret);
prptyBldr.SetGetMethod(currGetPropMthdBldr);
prptyBldr.SetSetMethod(currSetPropMthdBldr);
noNewProperties = false; }
if (noNewProperties == true)
{
return baseClassType; }
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)
{
ClassicP classicEx = DynamicFactory.CreateClass
<classicp>(GetExtendedType<classicp>(extendedColumnsDict));
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;
SetExtendedProperties(classicEx as dynamic, src, extendedColumnsDict);
return classicEx;
}
protected Type GetExtendedType<t>
(Dictionary<string> extendedColumnsDict) where T : class
{
if (ExtendedType == null)
{
ExtendedType = DynamicFactory.ExtendTheType<t>(ExtendedColumnsDict);
}
return ExtendedType;
}
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);
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