Saturday, June 16, 2007

Another runtime Class/Type generator using System.CodeDom

Another simple way to generate runtime class using codeDom.

C# library exposed different compiler for runtime compilation and anyone can use it for runtime type generation.

System.CodeDom is a library for runtime compilation, you can compile any code written for any language.

Lets, generate same set of functionality in class using system.CodeDom

Recall,

I have a interface actually I have type which is an interface and I want to generate a class implementing that interface.

Original code should be :

//interface
public interface IEcho
{
string Echo(string message);

string NetworkEcho(string message);
}
//Implementer
public class EchoProxy : IEcho
{
public string Echo(string message);

public string NetworkEcho(string message);
}

now I write a method which will take the type of interface as parameter and return the object of dynamic class.

object GetObject(Type interfaceType)

{

StringBuilder codeBlockBuilder = new StringBuilder();

// take new string builder to generate code.

codeBlockBuilder.AppendLine("using System;");

codeBlockBuilder.AppendLine("using System.Collections.Generic;");

codeBlockBuilder.AppendLine("using System.Text;");

codeBlockBuilder.AppendLine("using Interface;");

codeBlockBuilder.AppendLine("using System.Net;");

// Add necessary library for the code.In common java synonym import this libraries.

codeBlockBuilder.AppendLine("class " + interfaceType.Name + "DynamicProxy : " + interfaceType.FullName);

// Declare the class, here I don't use any name space, that is all this type goes to global namespace.

codeBlockBuilder.AppendLine("{");

MethodInfo[] methods = interfaceType.GetMethods();

// Get list of method define in the namespace

for (int methodIndex = 0; methodIndex < methods.Length; methodIndex++)

{

MethodInfo currentMethod = methods[methodIndex];

StringBuilder methodBuilder = new StringBuilder();

string typeName = currentMethod.ReturnType.FullName;

string declareString = null;

if (typeName == "System.Void")

// void is defined as System.Void which does not work as it is, so use void instead of System.Void

declareString = "void";

else

declareString = currentMethod.ReturnType.FullName;

methodBuilder.Append("public " + declareString + " " + currentMethod.Name + "(");

// define return type and method name.

for (int parameterIndex = 0; parameterIndex < currentMethod.GetParameters().Length; parameterIndex++)

{

methodBuilder.Append(currentMethod.GetParameters()[parameterIndex].ParameterType.FullName + " ");

methodBuilder.Append(currentMethod.GetParameters()[parameterIndex].Name);

if (parameterIndex < currentMethod.GetParameters().Length - 1)

methodBuilder.Append(", ");

}

// Here I build method parameter as defined in the interface..

methodBuilder.Append(")");

methodBuilder.AppendLine();

methodBuilder.AppendLine("{");

if (currentMethod.Name == "Echo")

{

methodBuilder.AppendLine("Console.WriteLine(\"Echo(Proxy)\");");

methodBuilder.AppendLine("return " + currentMethod.GetParameters()[0].Name + ";");

// return the parameter itself.

}

else if (currentMethod.Name == "NetworkEcho")

{

methodBuilder.AppendLine("Console.WriteLine(\"NetworkEcho(Proxy)\");");

methodBuilder.AppendLine("return " + currentMethod.GetParameters()[0].Name + " + \"[\" + Dns.GetHostName() + \"(\" + (Dns.GetHostByName(Dns.GetHostName())).AddressList[0].ToString() + \")]\";");

// Return the parameter appending network address.

}

methodBuilder.AppendLine("}");

codeBlockBuilder.AppendLine(methodBuilder.ToString());

}

codeBlockBuilder.AppendLine("}");

// Here ends my class generation, this is simple code so I don't describe it rigorously

Microsoft.CSharp.CSharpCodeProvider cp = new Microsoft.CSharp.CSharpCodeProvider();

// Take new Csharp provider class, this is the source of CSharp compiler.

System.CodeDom.Compiler.ICodeCompiler compiler = cp.CreateCompiler();

// Take new CSharp compiler, and assign it to the ICodeCompiler...

System.CodeDom.Compiler.CompilerParameters compilerParam = new System.CodeDom.Compiler.CompilerParameters();

// You can set different option for your compiler, with compiler parameter class.

compilerParam.GenerateInMemory = true;

// Generate the types in memory.

compilerParam.GenerateExecutable = false;

// Here is no executable start point, this is not executable.

compilerParam.ReferencedAssemblies.Add("system.dll");

compilerParam.ReferencedAssemblies.Add("Interface.dll");

// Add assembly to your runtime compiler.

System.CodeDom.Compiler.CompilerResults compilerResult = compiler.CompileAssemblyFromSource(compilerParam, codeBlockBuilder.ToString());

// Compile the code and get result from you runtime compiler

foreach (System.CodeDom.Compiler.CompilerError compilerError in compilerResult.Errors)

{

throw new Exception(compilerError.ErrorText);

}

// for each error I throw an exception.

if (compilerResult.Errors.Count == 0 && compilerResult.CompiledAssembly != null)

{

Type ObjType = compilerResult.CompiledAssembly.GetType(serviceClass.Name + "DynamicProxy");

// Now I am looking for my generated class

try

{

if (ObjType != null)

{

return Activator.CreateInstance(ObjType);

// If I found I create an object with activator and return that instance.

}

else

{

throw new Exception("Dynamic type not found.");

return null;

}

}

catch (Exception ex)

{

throw ex;

}

}

else

{

throw new Exception("Failed to compile");

return null;

}

// Others error handling

}

This is another way to generate runtime class, in this procedure you can generate runtime class but you cannot modify exiting class.

Again, you can save the code as well as compiled assembly.

So this is helpful for code generator, rather than dynamic proxy.

Again, this is very much slower than System.Reflection.Emit

So, it is better choice for code generation or assenbly generator because you don't have to write IL rather you are writing normal C# code.

No comments:

Post a Comment

Please, no abusive word, no spam.