Click here to Skip to main content
Click here to Skip to main content

Dynamic Assemblies

, 24 Aug 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
Creating Dynamic assemblies in C#

Introduction

In this article, I am going to take you through a scenario where you can use dynamically created assemblies in your software solutions. I will be focusing more on System.CodeDom namespace, but towards the end I will discuss the use of System.Reflection.Emit namespace that helps you to create dynamic assemblies.

Scenario

Consider business scenarios were due to market trends and other effects you decide to give your clients a special offer. This can change according to the time of the year, type of the item, etc. In addition, you will decide to give discounts for a certain selected item category according to the quantity or the total price. Furthermore, you want to change the validation rules of your application. For instance, you want to restrict the quantity of some item category that can be bought remotely.

Imagine that your system architecture is where your clients log in to your server using Windows application, using a socket or Remoting facilities provided in .NET Framework.

When you look at this problem, you will see that you cannot hard code these requirements into your client application. It’s purely because parameters and equations are decided by the server itself according to the market trends and to maximize the profit margins. So your client application must have the capability to be configured by the server. In addition in this type of architecture, you have to reduce the network communication and utilization of bandwidth unnecessarily. To tackle this problem, you can use the capabilities of the System.CodeDom namespace. The main feature provided by this technique is to generate source code at runtime in multiple languages. Then System.CodeDom.Compiler will give you freedom to compile the source code you dynamically created with relevant language compiler into a class library or an executable.

In my demo application, I am going to dynamically create an assembly and use it for discount calculations, special offer calculations and for some validations. Here I am going to consider a situation of a book shop, where there are a set of product categories such as Books, Magazines, Gifts and Stationary. There could be sub product categories as well. Below I have mentioned some of the rules and offers I am going to implement in this application.

Book category - “B”

  • 2% discount if the total value exceeds $50
  • 3.5% discount if the total value exceeds $150
  • 5.75% discount if the total value exceeds $400
  • 6% for first 500 and $5 discount for each $100 above 500.

Gift Category – “G”

  • Maximum order value that can be ordered online is 1000$

Magazine category - “M” and sub category Fashion – “F”

  • Maximum quantity that can be ordered online is 2
  • There is 2.5% discount if the total value exceeds $100

Magazine category - “M” and sub category Technical – “T”

  • If order value is over 750, there is a choice to select magazine from a set of magazines populated in the order window.

Stationary category - “S”

  • If the order value is over 500$ there is a choice to select a free gift from a set of gifts populated in the order window.

As I clearly mentioned, these values and the parameters will be changed according to the market trends and profit margins to be achieved.

Implementation

Even though I have used a Rules.xml XML document to store my dynamic rules sent from the sever, in real circumstances these rules are sent from the server as strings and they will change according to requirements. If you inspect the XML, you can see that in the body element what you have is C# code. Similarly ReturnType element is C# type. Imagine that Rule.xml is sent from the server or similar set of data is sent from the server. Then the next step is to include this in a C# class and create an assembly on the fly.

First let’s concentrate on how to create a class using the code segments sent from the server. CodeCompileUnit is the main object that maintains or models the structure of the source we are going to write also known as the CodeDOM graph of a source. Using this object, we can add specific namespaces we want, to import the referenced namespaces used in the source and define the types such as classes that will be declared inside that program structure. There are a set of classes in the System.Codedom namespace that is referred by this CodeCompileUnit class. Please go through the following code segment.

// Declare a new namespace 
CodeNamespace codeNamespace = new CodeNamespace("DynamicAssemblyDemo.DynamicAssembly"); 
// Add the new namespace to the compile unit. 
codeCompileUnit.Namespaces.Add(codeNamespace); 
// Add the new namespace import for the System namespace. 
codeNamespace.Imports.Add(new CodeNamespaceImport("System")); 
// Declare a new type called BookShopRuleAssembly. 
bookShopRuleClass = new CodeTypeDeclaration("BookShopRuleClass"); 
// Add the new type to the namespace type collection. 
codeNamespace.Types.Add(bookShopRuleClass);

In the above code segment, you can see that I have used CodeNamespace class to declare the namespace of the dynamic class I am going to create (DynamicAssemblyDemo.DynamicAssembly). Then I have imported System namespace which will be used in this class. The other important class that I have used here is CodeTypeDeclaration which represents a type declaration for a class, structure, etc. The name of the dynamic class is BookShopRuleClass.

In a CodeTypeDeclaration object, you can find a set of interesting properties to shape your type, in my case the class just like a normal class. For instance, you can add the comment to code, break the code into regions, you can add member fields and methods. I have used the Member property of this class extensively to add local fields and methods. CodeTypeMemberCollection is a collection of CodeTypeMember type which is the base class of many important types that can be added as members to a class. In the following table extracted from the MSDN, you can see those classes.

  • System.CodeDom.CodeMemberEvent: Represents a declaration for an event of a type
  • System.CodeDom.CodeMemberField: Represents a declaration for a field of a type
  • System.CodeDom.CodeMemberMethod: Represents a declaration for a method of a type
  • System.CodeDom.CodeMemberProperty: Represents a declaration for a property of a type
  • System.CodeDom.CodeSnippetTypeMember: Represents a member of a type using a literal code fragment
  • System.CodeDom.CodeTypeDeclaration: Represents a type declaration for a class, structure, interface, or enumeration

In this implementation, I have used CodeMemberField and CodeMemberMethod types to setup the class structure I want. Now let’s move in to the SetMethod and GetFieldCode methods.

CodeMemberField GetFieldCode(string name,CodeTypeReference fieldType) 
{ 
CodeMemberField field = new CodeMemberField(); 
field.Name = name; 
field.Attributes = MemberAttributes.Public; 
field.Type = fieldType; 
return field; 
}

The above code is straightforward. Here we create a field that will be added to the class we created. This CodeMemberField object is added to the Member collection of the bookShopRuleClass. Here is the sample code:

CodeMemberField tempField = GetFieldCode(“category”, 
	new CodeTypeReference(typeof(System.String))); 
bookShopRuleClass.Members.Add(tempField);//adding to the class 

Similarly I have added the methods that will be called from ultimately for validations and special offer calculations.

CodeMemberMethod SetMethod(string name,CodeTypeReference returnType,string methodBody)
{ 
CodeMemberMethod method = new CodeMemberMethod(); 
method.Name = name; 
// adding method body 
CodeSnippetStatement statement = new CodeSnippetStatement(methodBody); 
method.ReturnType = returnType; 
method.Statements.Add(statement); // adding the statement into the method 
method.Attributes = MemberAttributes.Public; 
return method; 
}

Main things you have to keep in mind are that rules you have to adhere to are the same as the normal class. You cannot have the same method signatures duplicated everywhere or have incorrect syntax inside the method bodies. To avoid the duplication of the same signature within the class, I have used the category code integrated with the method name. For example to GetDiscount_B() and GetDiscount_M() method signatures, avoid the duplication of the GetDiscount method inside the same class. In the above code CodeSnippetStatement class helps us a lot to insert a literal code segment into the source code without any modifications.

Now our BookShopRuleClass implementation is done. The whole implantation is inserted into the CodeCompileUnit object. The next step is to compile the code. For this vital step, we have to use the System.CodeDom.Compiler namespace. Given the code structure or CodeCompileUnit/s we can compile it using an appropriate compiler and generate assembly file or an executable. Here we have to use some derived class of CodeDomProvider to compile the structure. In this case, we have to use the CSharpCodeProvider class to compile our BookShopRuleClass. In this implementation, I used GenerateCodeFromCompileUnit and CompileAssemblyFromDom methods respectively to generate the source code and to get the compiled assembly. Apart from the CompileAssemblyFromDom method, there are two other methods to compile a code, namely CompileAssemblyFromFile and CompileAssemblyFromSource. I think the method says where you can use these methods.

After creating this assembly, it’s all Reflection afterwards. In the RuleManager class, using that assembly I have created a BookShopRuleClass instance and using reflection invoking the specific method accordingly. I will not go into details of RuleManager class since it’s not in the scope of this article. If you have some knowledge in Refection, you can easily understand the implementation there.

For the sake of completeness, I will talk about System.Reflection.Emit namespace very little which is another way of creating dynamic assemblies. However I must say that this is a very hectic and time consuming way. Also for the above scenario, this technique is a bit difficult to use. Even a simple mistake will cause the assembly creation process to fail. Please go through the code segment below. Before that, it is somewhat important to understand the opcodes and other syntax used in MSIL (Microsoft intermediate language). Also to get familiarized with this syntax, use the MSIL Disassembler (Ildasm.exe) tool and go through some of the Assemblies you created.

public Assembly CreatAssembly() 
{ 
AssemblyBuilder ab = null; 
try 
{ 
AssemblyName an = new AssemblyName(); // we can define assemblies unique identity 
// Version is 1.0.0.0 
an.Version = new Version(1, 0, 0, 0); 
// Set the assembly name 
an.Name = "BookShopRuleWithEmit"; 
// Define a dynamic assembly 
// AssemblyBuilderAccess : Defines the access mode of the assembly to Run 
ab = Thread.GetDomain().DefineDynamicAssembly(an, AssemblyBuilderAccess.RunAndSave); 
// Define a dynamic module and the filename of the assembly 
ModuleBuilder modBuilder = ab.DefineDynamicModule("BookShopRuleWithEmit",
"BookShopRuleWithEmit.dll"); 
// Create the public BookShopRules class (can specify the namespace if needed) 
TypeBuilder tb = modBuilder.DefineType("BookShopRuleWithEmit.BookShopRules",
TypeAttributes.Public); 
// Define two fields (both Public) 
FieldBuilder price = tb.DefineField("price", typeof(double), FieldAttributes.Public); 
FieldBuilder qty = tb.DefineField("quantity", typeof(double), FieldAttributes.Public); 
// Define GetDiscount public method, with return type double 
MethodBuilder adderBldr = tb.DefineMethod("GetDiscount", 
MethodAttributes.Public, 
CallingConventions.Standard, 
typeof(double), 
new Type[0]); 
//now the implementation of the method 
ILGenerator ilgen = adderBldr.GetILGenerator(); // ILGenerator emit the instructions. 
Label failed = ilgen.DefineLabel(); 
Label failed2 = ilgen.DefineLabel(); // to label the instruction. 
Label endOfMthd = ilgen.DefineLabel(); 
ilgen.Emit(OpCodes.Ldarg_0); 
ilgen.Emit(OpCodes.Ldfld, qty); 
ilgen.Emit(OpCodes.Ldarg_0); //condition one if((price*quantity) < 100) 
ilgen.Emit(OpCodes.Ldfld, price); //return ((price*quantity)*.025) 
ilgen.Emit(OpCodes.Mul); 
ilgen.Emit(OpCodes.Ldc_R8, 100.00); 
ilgen.Emit(OpCodes.Bgt_S, failed); 
ilgen.Emit(OpCodes.Ldarg_0); 
ilgen.Emit(OpCodes.Ldfld, qty); 
ilgen.Emit(OpCodes.Ldarg_0); 
ilgen.Emit(OpCodes.Ldfld, price); // Body of the statement 
ilgen.Emit(OpCodes.Mul); 
ilgen.Emit(OpCodes.Ldc_R8, 0.025); 
ilgen.Emit(OpCodes.Mul); 
ilgen.Emit(OpCodes.Br_S, endOfMthd); 
ilgen.MarkLabel(failed); 
ilgen.Emit(OpCodes.Ldarg_0); // condition two 
ilgen.Emit(OpCodes.Ldfld, qty); // else if ((price*quantity) < 500) 
ilgen.Emit(OpCodes.Ldarg_0); //return ((price*quantity)*.05) 
ilgen.Emit(OpCodes.Ldfld, price); 
ilgen.Emit(OpCodes.Mul); 
ilgen.Emit(OpCodes.Ldc_R8, 500.00); 
ilgen.Emit(OpCodes.Bgt_S, failed2); 
ilgen.Emit(OpCodes.Ldarg_0); 
ilgen.Emit(OpCodes.Ldfld, qty); 
ilgen.Emit(OpCodes.Ldarg_0); 
ilgen.Emit(OpCodes.Ldfld, price); // body of else if 
ilgen.Emit(OpCodes.Mul); 
ilgen.Emit(OpCodes.Ldc_R8, 0.05); 
ilgen.Emit(OpCodes.Mul); 
ilgen.Emit(OpCodes.Br_S, endOfMthd); 
ilgen.MarkLabel(failed2); 
ilgen.Emit(OpCodes.Ldarg_0); 
ilgen.Emit(OpCodes.Ldfld, qty); 
ilgen.Emit(OpCodes.Ldarg_0); 
ilgen.Emit(OpCodes.Ldfld, price); // body of else return ((price*quantity)*.075) 
ilgen.Emit(OpCodes.Mul); 
ilgen.Emit(OpCodes.Ldc_R8, 0.075); 
ilgen.Emit(OpCodes.Mul); 
ilgen.Emit(OpCodes.Br_S, endOfMthd); 
ilgen.MarkLabel(endOfMthd); // returning from the method 
ilgen.Emit(OpCodes.Ret); 
tb.CreateType(); 
ab.Save("BookShopRuleWithEmit.dll"); // saving the DLL in the disk 
} 
catch (Exception e) 
{ 
} 
return ab; 
} 

Conclusion

Now you can see the value of the System.CodeDom namespace. Using dynamically generated assembly, we can reduce the server client conversations immensely, hence optimizing the network usage as well as reducing the sever load.

Reference

Acknowledgements

  • Mr. Rohan Mapatuna
  • Mr. Uditha Bandara

History

  • 24th August, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

PhD, BSc(Eng), MCP (Web Development)

Comments and Discussions

 
GeneralMy vote of 2 Pinmembergyanender sharma8-Jun-11 21:20 
nj

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.141022.2 | Last Updated 24 Aug 2008
Article Copyright 2008 by Tharindu Nishad Patikirikorala
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid