One not so well known
feature of .NET platform is possibility to invoke compiler and practically
create code and assembly from running instance of application. It is possible to
do that in two ways. First one is a bit simpler and involves namespaces
System.CodeDom and System.CodeDom.Compiler, second one is more efficient and
utilizes namespace System.Reflection.Emit. Since there are very few examples
about how to use System.CodeDom.Compiler I will start with it.
System.CodeDom.Compiler introductory example
As usual introductory example is about writing assembly which will
write another assembly which will write to console string (incidentally "Hello
World") received from first assembly. Very convenient. Here is code to do all
that:
Imports System
Imports System.CodeDom
Imports System.CodeDom.Compiler
Imports System.Reflection
Class test
Shared Sub Main()
Dim compunit As New CodeCompileUnit
Dim TheName As New CodeNamespace("TheName")
compunit.Namespaces.Add(TheName)
TheName.Imports.Add(New CodeNamespaceImport("System"))
Dim Class1 As New CodeTypeDeclaration("Class1")
TheName.Types.Add(Class1)
Dim Say As New CodeMemberMethod
Say.Parameters.Add(New CodeParameterDeclarationExpression(GetType(String),
"s"))
Say.Statements.Add(New CodeExpressionStatement(NewCodeSnippetExpression("System.Console.WriteLine(s)")))
Class1.TypeAttributes = TypeAttributes.Public
Class1.Members.Add(Say)
Say.Attributes = MemberAttributes.Public
Say.Name = "Say"
Dim compparams As New CompilerParameters(New String()
{"mscorlib.dll"})
compparams.GenerateInMemory = True
'uncomment
following if you like to write dll to disk
'compparams.OutputAssembly=" HelloWorld.dll";
Dim csharp As New Microsoft.CSharp.CSharpCodeProvider
Dim cscompiler As ICodeCompiler
= csharp.CreateCompiler()
Dim compresult As CompilerResults
= cscompiler.CompileAssemblyFromDom(compparams, compunit)
If compresult Is Nothing Or compresult.Errors.Count
> 0 Then
Environment.Exit(1)
End If
Dim o As Object =
compresult.CompiledAssembly.CreateInstance("TheName.Class1")
Dim test As Type
= compresult.CompiledAssembly.GetType("TheName.Class1")
Dim m As MethodInfo
= test.GetMethod("Say")
Dim arg(1) As Object
arg(0) = " Hello World!"
m.Invoke(o, arg)
End Sub 'Main
End Class 'test
More or less code is
self-explanatory, you will declare namespace, imports, type and its name, method
and its name and so on. Only unusual thing is:
Say.Statements.Add(New CodeExpressionStatement(NewCodeSnippetExpression("System.Console.WriteLine(s)")))
which allows you to
insert expression using familiar syntax into the body of the method. Finally we
will invoke compiler and using System.Reflection activate instance of that newly
compiled assembly. I wanted to have that taking place in memory but if you like
to examine more closely result of compilation uncomment:
//compparams.OutputAssembly=" HelloWorld.dll"
If you are not sure
that example is sufficient don't worry there is another one later in the article
and also you can try to find on the web example published by Clemens Vasters -
(c) 2001 newtelligence AG. To try to explain everything would be quite difficult
so I must ask readers to use here .NET Framework SDK Documentation to find out
more about System.CodeDom.
Introductory example for System.Reflection.Emit was supplied by Microsoft and
link to it could be found on "Microsoft .NET Framework SDK QuickStarts,
Tutorials and Samples".
Now when you went through it and got understanding of these two namespaces it is
time to attend to some weaknesses of C# and IL in particular.
Mimicking C++ templates from
VB.NET
Since IL doesn't support templates you can't have
them supported in VB.NET or VB. I'm talking about C++ template here and this is
elementary example:
template <class T>
T Sum(T a, T b)
{
return a
+ b;
}
void main()
{
cout << Sum(2, 3) << '\n'
<< Sum(1.1, 2.2) << '\n';
}
Only at runtime will
be known type of variable and return type. Why are they important? During many
years they were the base for generic programming and due to popularity of C++
most of generic software using them. Before I start long-lasting discussion let
me mention interfaces. If that function should return bigger of two values we
will use interface IComparable which is defined in System but there is no
defined IAddible so I will also skip defining it. How is VB.NET handling the
same problem? Usually using Object where casting to Object is certain because
all types must inherit from it.
But object doesn't know how to add itself to another object. So you will have to
roll out new class where operator + knows how to add two objects depending on
their original type. Conventional solution of this problem I am leaving to
readers and I'm jumping right onto unusual one. First comes the easy one
involving System.CodeDom.Compiler.
Imports System
Imports System.CodeDom
Imports System.CodeDom.Compiler
Imports System.Reflection
Class T
Shared Sub Main()
Console.Write("Value = {0}" + ControlChars.Tab + "Type = {1}" + ControlChars.Lf,
Mimic(1, 2).ToString(), Mimic(1, 2).GetType())
Console.Write("Value = {0}" + ControlChars.Tab + "Type = {1}" + ControlChars.Lf,
Mimic(1.1, 3.14).ToString(), Mimic(1.1, 3.14).GetType())
Console.Write("Value = {0}" + ControlChars.Tab + "Type = {1}" + ControlChars.Lf,
Mimic("Hello ", "World!").ToString(), Mimic("Hello ",
"World!").GetType())Console.Read()
End Sub 'Main
Shared Function Mimic(ByVal a As Object, ByVal b As Object) As Object
If a.GetType()
<> b.GetType() Then
Environment.Exit(1)
End If
Dim rt As Type
= a.GetType()
Dim compunit As New CodeCompileUnit
Dim TheName As New CodeNamespace("TheName")
compunit.Namespaces.Add(TheName)
TheName.Imports.Add(New CodeNamespaceImport("System"))
Dim Class1 As New CodeTypeDeclaration("Class1")
TheName.Types.Add(Class1)
Dim AddT As New CodeMemberMethod
AddT.ReturnType = New CodeTypeReference(rt)
AddT.Parameters.Add(New CodeParameterDeclarationExpression(rt,
"a"))
AddT.Parameters.Add(New CodeParameterDeclarationExpression(rt,
"b"))
AddT.Statements.Add(New CodeMethodReturnStatement
(New CodeSnippetExpression("a+b")))
Class1.TypeAttributes = TypeAttributes.Public
Class1.Members.Add(AddT)
AddT.Attributes = MemberAttributes.Public
AddT.Name = "AddT"
Dim compparams As New CompilerParameters(New String()
{"mscorlib.dll"})
compparams.GenerateInMemory = True
'compparams.OutputAssembly="factory.dll";
Dim csharp As New Microsoft.CSharp.CSharpCodeProvider
Dim cscompiler As ICodeCompiler
= csharp.CreateCompiler()
Dim compresult As CompilerResults
= cscompiler.CompileAssemblyFromDom(compparams, compunit)
If compresult Is Nothing Or compresult.Errors.Count
> 0 Then
Environment.Exit(1)
End If
Dim o As Object =
compresult.CompiledAssembly.CreateInstance("TheName.Class1")Dim test As Type
= compresult.CompiledAssembly.GetType("TheName.Class1")
Dim m As MethodInfo
= test.GetMethod("AddT")
Dim arg(2) As Object
arg(0) = a
arg(1) = b
Dim result As Object =
m.Invoke(o, arg)
Return result
End Function 'Mimic
End Class 'T
Code is self-explanatory and similar to first example.
Again if you like you can write assembly to disc by means of doing uncomment of
following line:
//compparams.OutputAssembly="factory.dll"
Speed is far from
desirable but option to pass different code snippet together with variable if
desired makes this approach even more interesting.
In order to gain more speed it is necessary to use System.Reflection.Emit, IL
knowledge is also required. Following is my raw code where OpCodes.Add and
OpCodes. Add_Ovf (for integer types) should be altered accordingly, also
checking if both values making pair are of same type is necessary; if they are
not it is possible to try to convert them to same type and if that is not
possible, it is always possible to throw exception. Since this code is intended
only as illustration and in order to make it shorter I omitted these steps.
Imports System
Imports System.IO
Imports System.Reflection
Imports System.Reflection.Emit
Imports System.Threading
Public Class ASM
Public Function build(ByVal currentType As String) As Type
Dim assemblyName As New AssemblyName
assemblyName.Name = "genAssembly"
Dim [assembly] As AssemblyBuilder
=
Thread.GetDomain().DefineDynamicAssembly(assemblyName,AssemblyBuilderAccess.Run)
Dim [module] As ModuleBuilder
= [assembly].DefineDynamicModule("genAssembly") ',
"genAssembly.dll");
Dim genClass As TypeBuilder
= [module].DefineType("Gen", TypeAttributes.Public)Dim valField AsFieldBuilder
= genClass.DefineField("_val", Type.GetType currentType),
FieldAttributes.Private)
Dim arg(1) As Type
arg(0) = Type.GetType(currentType)
Dim cb As ConstructorBuilder
= genClass.DefineConstructor(MethodAttributes.Public,
CallingConventions.Standard, arg)
Dim ctorIL As ILGenerator
= cb.GetILGenerator()
ctorIL.Emit(OpCodes.Ldarg_0)
Dim objectClass As Type
= Type.GetType("System.Object")
Dim bc As ConstructorInfo
= objectClass.GetConstructor(New Type(0)
{})
ctorIL.Emit(OpCodes.Call, bc)
ctorIL.Emit(OpCodes.Ldarg_0)
ctorIL.Emit(OpCodes.Ldarg_1)
ctorIL.Emit(OpCodes.Stfld, valField)
ctorIL.Emit(OpCodes.Ret)
Dim mb As MethodBuilder
= genClass.DefineMethod("GetVal", MethodAttributes.Public OrMethodAttributes.HideBySig,
Type.GetType
(currentType), Nothing)
Dim getValMethod As MethodBuilder
= mb
Dim methodIL As ILGenerator
= mb.GetILGenerator()
methodIL.Emit(OpCodes.Ldarg_0)
methodIL.Emit(OpCodes.Ldfld, valField)
methodIL.Emit(OpCodes.Ret)
arg = New Type(2)
{}
arg(0) = genClass
arg(1) = genClass
mb = genClass.DefineMethod("op_Addition", MethodAttributes.Public Or MethodAttributes.Static OrMethodAttributes.HideBySig Or MethodAttributes.SpecialName,
genClass, arg)
methodIL = mb.GetILGenerator()
methodIL.Emit(OpCodes.Ldarg_0)
methodIL.Emit(OpCodes.Callvirt, getValMethod)
methodIL.Emit(OpCodes.Ldarg_1)
methodIL.Emit(OpCodes.Callvirt, getValMethod)
methodIL.Emit(OpCodes.Add)
methodIL.Emit(OpCodes.Newobj, cb)
methodIL.Emit(OpCodes.Ret)
Dim argTypes(1) As Type
argTypes(0) = Type.GetType(currentType)
Dim convToStrMethod As MethodInfo
= GetType(Convert).GetMethod("ToString",
argTypes)
arg = New Type(1)
{}
arg(0) = genClass
mb = genClass.DefineMethod("op_Implicit", MethodAttributes.Public Or MethodAttributes.Static OrMethodAttributes.HideBySig Or MethodAttributes.SpecialName,
Type.GetType("System.String"), arg)
methodIL = mb.GetILGenerator()
methodIL.Emit(OpCodes.Ldarg_0)
methodIL.Emit(OpCodes.Callvirt, getValMethod)
methodIL.Emit(OpCodes.Call, convToStrMethod)
methodIL.Emit(OpCodes.Ret)
Return genClass.CreateType()
End Function 'build
End Class 'ASM
Public Class Pair
Private a,
b As Object
Private current As String
Public Sub New(ByVal A As Object, ByVal B As Object)
a = A
b = B
current = A.GetType().ToString()
End Sub 'New
Public Sub Sum()
Dim asm As New ASM
Dim t As Type
= asm.build(current)
a = Activator.CreateInstance(t, New [Object]()
{a})
b = Activator.CreateInstance(t, New [Object]()
{b})
Dim obj As [Object]
= t.InvokeMember("op_Addition",
BindingFlags.InvokeMethod, Nothing,
a, New [Object]()
{a, b})
obj = t.InvokeMember("GetVal", BindingFlags.InvokeMethod, Nothing,
obj,Nothing)Console.WriteLine("Type
is " + obj.GetType() + " and sum={0}", obj.ToString())
End Sub 'Sum
End Class 'Pair
Class Pair is a
friendly wrapper and saves user from System.Reflection.Emit intricacies. The
code is intended to be compiled as library. Strings as pair are not supported;
to support them it could be done inside class Pair on high level language.
Immediately at the beginning of Sum insert something like this:
If current
= "System.String" Then
Console.WriteLine((Convert.ToString(a) + Convert.ToString(b)))
Return
End If
Changing "current"
from string to type and accordingly "build" method to accept type instead of
string eliminates few Type.GetType(currentType) calls but I didn't find any
significant performance improvement. So here is code from test app:
Dim p As New Pair(1,
2)
p.Sum()
p = New Pair(1.2,
2.4)
p.Sum()
p = New Pair(3.2F,
5.4F)
p.Sum()
Console.Read()
Don't forget to
reference library vbc /r:<file name> ...
Going from comfort related with writing code in high level language like VB.NET
or VB to difficulties related with work in assembly language will be probably
big minus for this approach if we are talking about the number of potential
users (developers, not final users). But increase in speed is significant, while
first example takes (on my machine which is not very fast) around 5 seconds to
execute, second executes in 0.2 seconds. Changing significantly the content of
the method in the second example will probably result in work similar to that
one which is required for writing compiler. But if we restrict number of
possible methods it is always possible to prepare number of templates for
creation of IL code, so that would make these changes possible to some degree.
Combining that with factory pattern could result in something that is not that
difficult to work with.