Wednesday, July 28, 2010

Generate .NET Code Dynamically

As I can remember, when I was starting learn to programming, writing a simple Hello World program was a big challenge, even though we had enough capable IDEs to do the programming. It’s very interesting to even imagine generate programs dynamically at run time by another program.

With Microsoft .NET CodeDom, it’s possible to generate programs, compile and run them at run time. With this framework code library, it’s possible to have virtual DLLs, Namespaces, classes, Variables, Methods and etc.

Just Imagine this is the class you need to generate dynamically,

namespace ArticlesCodeArchive

{

public class SampleClass

{

private string _property1;

private bool _property2;

public SampleClass(string tech)

{

this._property1 = tech;

this._property2 = false;

}

public string Property1

{

get { return _property1; }

set { _property1 = value; }

}

public bool Property2

{

get { return _property2; }

set { _property2 = value; }

}

}

}

Create a Namespace

public static CodeNamespace CreateNameSpace(string nameSpaceName)

{

CodeNamespace returnValue =

new CodeNamespace(nameSpaceName);

returnValue.Imports.Add

(new CodeNamespaceImport("System"));

returnValue.Imports.Add

(new CodeNamespaceImport("System.Text"));

return returnValue;

}

Method is returning a reference to a CodeNamespace object, which is representing a Namespace with provided name.

Create Class Declaration

public static CodeTypeDeclaration CreateType(string name)

{

CodeTypeDeclaration returnValue = new CodeTypeDeclaration(name);

returnValue.IsClass = true;

returnValue.BaseTypes.Add(new CodeTypeReference(typeof(Object)));

return returnValue;

}

The Class name you have to pass into the method and it will return a reference of object type CodeTypeDeclaration class, which is representing a particular .NET Class with given name.

Create a Property

public static CodeMemberProperty CreateProperty(string Name, Type propertyType)

{

CodeMemberProperty returnValue = new CodeMemberProperty();

returnValue.Attributes = MemberAttributes.Public;

returnValue.Name = Name;

returnValue.Type = new CodeTypeReference(propertyType);

returnValue.GetStatements.Add(

new CodeMethodReturnStatement(new

CodeVariableReferenceExpression("_" + Name)));

returnValue.SetStatements.Add(new CodeAssignStatement(

new CodeVariableReferenceExpression("_" + Name),

new CodeVariableReferenceExpression("value")));

return returnValue;

}

As Same as Class declaration, we have to pass Property Name and the type of the property here and method will return an object reference of type CodeMemberProperty Class, which is representing the .NET Class Property virtually.

Generate a Class

Let’s generate the class we need with all together,

public static CodeNamespace GenerateSkeleton()

{

CodeNamespace returnvalue = CreateNameSpace("ArticlesCodeArchive");

CodeTypeDeclaration generatedType = CreateType("SampleClass");

returnvalue.Types.Add(generatedType);

generatedType.Members.Add(CreateProperty("property1", typeof(string)));

generatedType.Members.Add(

new CodeMemberField(typeof(string), "_property1"));

generatedType.Members.Add(CreateProperty("property2", typeof(string)));

generatedType.Members.Add(

new CodeMemberField(typeof(string), "_property2"));

return returnvalue;

}

public void GenerateCode()

{

CodeNamespace cns = GenerateSkeleton();

return cns.ToString();

}

Now we have generated the class we need and to make sure everything is ok, you just write the text coming from GenerateCode() method to a file. And it will exactly match with the class we mentioned at the start of this article.

Compile the Generated Class

Now it’s time to working on compiling the class generated at runtime. As same as class generation, compilation is also going to be done at runtime.

public Assembly CompileCode()

{

CodeNamespace ns = GenerateSkeleton();

string lcCode = GenerateCode("C#", ns);

ICodeCompiler loCompiler = new CSharpCodeProvider().CreateCompiler();

CompilerResults loCompiled = GetCompiler(lcCode, loCompiler);

if (loCompiled.Errors.HasErrors)

{

string lcErrorMsg = "";

lcErrorMsg = loCompiled.Errors.Count.ToString() + " Errors:";

for (int x = 0; x <>

lcErrorMsg = lcErrorMsg + "\r\nLine: " +

loCompiled.Errors[x].Line.ToString() + " - " +

loCompiled.Errors[x].ErrorText;

//MessageBox.Show(lcErrorMsg + "\r\n\r\n" + lcCode,

// "Compiler Demo");

return null;

}

Assembly loAssembly = loCompiled.CompiledAssembly;

Return loAssembly;

}

At the end of the compilation, you can have compiled assembly and this is not an assembly physically exists.

How to use a virtual assembly

After generating the Assembly for the virtual class implementation, it’s time to use it at runtime. This class won’t be alive to use later or after a particular session of the application, because all these things are happening here, is virtual. Anything you need to keep for the later use, you will have to save them purposely in the code.

public void UseVirtualClass()

{

Assembly loAssembly = CompileCode();

object loObject = loAssembly.CreateInstance("ArticlesCodeArchive.SampleClass");

object loResult = null;

loResult = loObject.GetType().InvokeMember("property1", BindingFlags.DeclaredOnly |

BindingFlags.Public | BindingFlags.NonPublic |

BindingFlags.Instance | BindingFlags.SetProperty, null, loObject, new Object[] { "This is the value for propert1" });

loResult = loObject.GetType().InvokeMember("property2", BindingFlags.DeclaredOnly |

BindingFlags.Public | BindingFlags.NonPublic |

BindingFlags.Instance | BindingFlags.SetProperty, null, loObject, new Object[] { "This is the value for propert2" });

}

That’s all, now we have assigned values for an object of the class that what we generated dynamically and all these things are happening at runtime.

Lakmal Kankanamge