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.