Meditation and Mindfulness for Software / IT Professionals. Conducted by Bipin Joshi in Thane. Read more...

<%@ Page %>

Creating Assemblies On The Fly Using Code DOM

Introduction

The .NET Code Document Object Model (CodeDOM) classes allow you to create assemblies on the fly. This provides a powerful way to parse and emit compiled code via code. ASP.NET itself uses this technique internally. In this article I will show you how to create an executable consisting of a class, property and methods. After going through the article you will get a good feel of how CodeDOM works and how to work with some of the commonly used classes.

Parts of an Assembly

A typical assembly consists of following parts:
  • Namespaces
  • using statements
  • Types within namespaces i.e. classes, structures and enumerations
  • Member variables with types
  • Properties within types
  • Methods within types
  • Entry Point in case of executables
In our example we will generate an assembly with following parts:
  • Namespace - MyNamespace
  • Class - CMyClass
  • Entry point - Main
  • Member variable - strMessage
  • Property - Message
  • Method - AddNumbers
We will finally save this assembly as HelloWorld.exe.

Sample Application

We will now create a sample console application that will generate, compile and emit HelloWorld.exe on the fly. Following is the empty definition of the class.
namespace CodeDomDemo
{
	public class CCodeGenerator
	{
		//our code goes here
		static void Main(string[] args)
		{
			//...
		}
	}
}
We created a console application called CCodeGenerator that in turn will generate HelloWorld.exe for us. We will write following methods to the CCodeGenerator class.
  • CreateNamespace();
  • CreateImports();
  • CreateClass();
  • CreateMember();
  • CreateProperty();
  • CreateMethod();
  • CreateEntryPoint();
  • SaveAssembly();
The method names are self explanatory and indicate what they will be doing.

Class level variables

We have split the functionality of creating HelloWorld.exe into many small methods. They need access to some common things such as namespace and class being generated. Hence, we will declare three variables at class level that can then be accessed from inside the methods. The classes mentioned below reside in System.CodeDom and System.CodeDom.Compiler namespaces.
CodeNamespace mynamespace;
CodeTypeDeclaration myclass;
CodeCompileUnit myassembly;
The CodeNamespace class represents a namespace of the assembly to be generated. The CodeTypeDeclaration class represents the class, structure or enumeration inside the namespace. Finally, CodeCompileUnit class represents the actual assembly i.e. unit of compilation under consideration.

Creating Namespance

The CreateNamespace method creates a name space MyNamespace as shown below:
public void CreateNamespace()
{
	mynamespace= new CodeNamespace();
	mynamespace.Name = "MyNamespace";
}

Adding using statements

Most commonly your namespace will contain at least one using statement (Imports in VB.NET). The CreateImports method adds such statements as shown below:
public void CreateImports()
{
	mynamespace.Imports.Add
	(new CodeNamespaceImport("System"));

	mynamespace.Imports.Add
	(new CodeNamespaceImport("System.Drawing"));

	mynamespace.Imports.Add
	(new CodeNamespaceImport("System.Windows.Forms"));
}
The CodeNamespaceImport class represents a namespace to be imported and its constructor accepts the string representing the namespace to be imported.

Creating Class

Next, the CreateClass method will add class to the namespace we created above.
public void CreateClass()
{
	myclass = new CodeTypeDeclaration();
	myclass.Name = "CMyClass";
	myclass.IsClass = true;
	myclass.Attributes = MemberAttributes.Public;
	mynamespace.Types.Add(myclass);
}
The Name property represents the name of the class (CMyClass in our case) to be created. The IsClass property indicates whether the type is a class or structure or enumeration. The Attributes property represents the access modifier for the class (Public in our case). Then we add the class to the namespace.

Adding class member variables

The CreateMember method adds member variables to the class we created above.
public void CreateMember()
{
	CodeMemberField mymemberfield=
	new CodeMemberField(typeof(System.String), "strMessage");
	myclass.Members.Add(mymemberfield);
}
The CodeMemberField class represents a member variable of a class. We have set its data type to be string and name as strMessage. We then add it to the class we created above.

Creating Properties

The CreateProperty method creates a property called Message.
public void CreateProperty()
{
	CodeMemberProperty myproperty=
	new CodeMemberProperty();
	myproperty.Name="Message";
	myproperty.Attributes=MemberAttributes.Public;
	CodeSnippetExpression getsnippet=
	new CodeSnippetExpression("return strMessage");

	CodeSnippetExpression setsnippet=
	new CodeSnippetExpression("strMessage=value");
	myproperty.GetStatements.Add(getsnippet);
	myproperty.SetStatements.Add(setsnippet);
	myclass.Members.Add(myproperty);
}
The CodeMemberProperty class represents a property of the class. The Name property indicates the name of the property and the Type property indicates the data type of the property. The property consist of code statements represented by CodeSnippetExpression class. You can add code statements to the Set and Get property using GetStatements and SetStatements collections. Finally we add the property to the Members collection of the class.

Creating methods

The CreateMethod method creates a method called AddNumbers.
public void CreateMethod()
{
	CodeMemberMethod mymethod = new CodeMemberMethod();
	mymethod.Name="AddNumbers";
	CodeParameterDeclarationExpression cpd1=
	new CodeParameterDeclarationExpression(typeof(int),"a");
	CodeParameterDeclarationExpression cpd2=
	new CodeParameterDeclarationExpression(typeof(int),"b");
	mymethod.Parameters.Add(cpd1);
	mymethod.Parameters.Add(cpd2);
	CodeTypeReference ctr=
	new CodeTypeReference("System.Int32");
	mymethod.ReturnType=ctr;
	CodeSnippetExpression snippet1=
	new CodeSnippetExpression
	("System.Console.WriteLine
	(\"Adding :\" + a + \" And \" + b )");
	CodeSnippetExpression snippet2=
	new CodeSnippetExpression("return a+b");
	CodeExpressionStatement stmt1=
	new CodeExpressionStatement(snippet1);
	CodeExpressionStatement stmt2=
	new CodeExpressionStatement(snippet2);
	mymethod.Statements.Add(stmt1);
	mymethod.Statements.Add(stmt2);
	mymethod.Attributes=MemberAttributes.Public;
	myclass.Members.Add(mymethod);
}
Here, we created a method that accepts two integer parameters, adds them and returns the resulting integer. The CodeParameterDeclarationExpression class represents the method parameter. CodeTypeReference class represents the return type here and is set to the ReturnType property of the method. The CodeSnippetExpression class represents the statements of the method as before. This time we add the statements to the Statements collection. Then we set the access modifier to public via Attributes property. Finally we add the method to the Members collection of the class.

Creating the Entry Point

Now that we are ready with members, properties and methods of the class, it is time to create the entry point for it. For an executable the entry point is static Main method.
public void CreateEntryPoint()
{
	CodeEntryPointMethod mymain = new CodeEntryPointMethod();
	mymain.Name="Main";
	mymain.Attributes=
	MemberAttributes.Public | MemberAttributes.Static;
	CodeSnippetExpression snippet1=
	new CodeSnippetExpression("CMyClass x=new CMyClass()");
	CodeSnippetExpression snippet2=
	new CodeSnippetExpression
	("Console.WriteLine(\"Answer: {0}\",x.AddNumbers(10,20))");
	CodeSnippetExpression snippet3=
	new CodeSnippetExpression("x.Message=\"Hello World\"");
	CodeSnippetExpression snippet4=
	new CodeSnippetExpression("Console.WriteLine(x.Message)");
	CodeSnippetExpression snippet5=
	new CodeSnippetExpression("Console.ReadLine()");

	CodeExpressionStatement stmt1=
	new CodeExpressionStatement(snippet1);
	CodeExpressionStatement stmt2=
	new CodeExpressionStatement(snippet2);
	CodeExpressionStatement stmt3=
	new CodeExpressionStatement(snippet3);
	CodeExpressionStatement stmt4=
	new CodeExpressionStatement(snippet4);
	CodeExpressionStatement stmt5=
	new CodeExpressionStatement(snippet5);

	mymain.Statements.Add(stmt1);
	mymain.Statements.Add(stmt2);
	mymain.Statements.Add(stmt3);
	mymain.Statements.Add(stmt4);
	mymain.Statements.Add(stmt5);

	myclass.Members.Add(mymain);
}
The CodeEntryPointMethod represents the entry point for the class. Most of the code above is same as in the AddNumbers method as Main is nothing but another method. The method of the compiled assembly will create an instance of CMyClass and call its properties and method.

Actual CMyClass code

What we did on the fly above is actually equivalent to following code:
using System;
using System.Drawing;
using System.Windows.Forms;

namespace MyNamespace
{
	public class CMyClass
	{
		string strMessage;

		public static void Main()
		{
			CMyClass x=new CMyClass();
			x.Message="Hello World";
			Console.WriteLine(x.Message);
			Console.WriteLine
			("Answer :{0}",x.AddNumbers(10,20));
			Console.ReadLine();

		}
		public string Message
		{
			get
			{
				return strMessage;
			}
			set
			{
				strMessage=value;
			}
		}

		public int AddNumbers(int a,int b)
		{
			return a+b;
		}
	}
}

Compiling the Assembly

Now the final step. Compiling the assembly and saving it as HelloWorld.exe.
public void SaveAssembly()
{
	myassembly= new CodeCompileUnit();
	myassembly.Namespaces.Add(mynamespace);
	CompilerParameters compparams =
	new CompilerParameters(new string[]{"mscorlib.dll"});
	compparams.ReferencedAssemblies
	.Add("System.dll");
	compparams.ReferencedAssemblies
	.Add("System.Drawing.dll");
	compparams.ReferencedAssemblies
	.Add("System.Windows.Forms.dll");
	compparams.GenerateInMemory=false;
	compparams.GenerateExecutable=true;
	compparams.MainClass="MyNamespace.CMyClass";
	compparams.OutputAssembly=
	@"c:\codedomtest\HelloWorld.exe";
	Microsoft.CSharp.CSharpCodeProvider csharp =
	new Microsoft.CSharp.CSharpCodeProvider();

	ICodeCompiler cscompiler = csharp.CreateCompiler();
	CompilerResults compresult =
	cscompiler.CompileAssemblyFromDom(compparams,myassembly);

	if(compresult == null || compresult.Errors.Count>0)
	{
		for(int i=0;i<compresult.Errors.Count;i++)
		{
			Console.WriteLine(compresult.Errors[i]);
		}
		Console.ReadLine();
		Environment.Exit(1);
	}
}
Here, we add the namespace to the CodeCompilationUnit. The referenced assemblies will be indicated via CompilerParameters class. The code you generate can be used in-memory using reflection or can be saved to disk. This can be controlled by GenerateInMemory property. If the output type is executable then you also need to set the GenerateExecutable property to true. The compiler also needs to know the class that contains the entry point. This is done via MainClass property. The OutputAssembly property indicates the path and name of the assembly to be saved. The classes of CodeDOM are compiler neutral. Here, we want to compile the code using C# compiler. That can be done via CSharpCodeProvider class. The actual compilation task is done by CompileAssemblyFromDom method and the compilation results are stored in CompilerResults object.

Testing the application

Now compile the CCodeGenerator class either using VS.NET or command line compiler and run the resulting exe. This will produce HelloWorld.exe at the specified path (c:\codedomtest\HelloWorld.exe) in our case. Then run the HelloWorld.exe and you should see Hello World message followed by result of addition of the numbers (10 and 20).



Bipin Joshi is a software consultant, an author and a yoga mentor having 21+ years of experience in software development. He conducts online courses in ASP.NET MVC / Core, jQuery, and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced Yoga way of life he also teaches Meditation to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 15 Feb 2003



Tags : .NET Framework VB.NET C# Components