Introduction
Many developers have found the magic of emitting code at runtime using Reflection.Emit
. Also, many have not used it simply because of the complexity of the API. RunSharp
(or Run# if you prefer), aims to bring the simplicity and readability of a high-level language such as C# to runtime code generation.
The IL is a great language. However, there are several problems with emitting IL instructions directly. One of them is that it's quite hard to see the actual structure of the generated code, by just looking at series of Emit()
calls. Also, there are strict rules as to what sequences of IL you can emit, unless you want to receive the awful 'Common Language Runtime detected an invalid program' exception. Sometimes, it's hard to find out exactly what IL is needed to achieve some simple C# concept. And there are more. I've created RunSharp
to avoid these problems, and I've had a lot of fun writing it. And I hope you will have a lot of fun using it.
Using the Code
There is very little you need to know before you start emitting your next great assembly. Here are the simple steps to your first Hello World generated at runtime:
1. Create an Assembly
AssemblyGen ag = new AssemblyGen("HelloWorld.exe");
This call simply creates an AssemblyBuilder
and ModuleBuilder
under the hood.
2. Define Your First Class
TypeGen MyClass = ag.Public.Class("MyClass");
{
}
As you can see, this is very similar to what you would write in C#, e.g.:
public class MyClass
{
}
You can also use other modifiers for the class, such as Private
, Sealed
, Abstract
or NoBeforeFieldInit
(the last allows the static
constructor to be invoked sooner or later than any class member is accessed - see an excellent article on beforefieldinit written by Jon Skeet for some details).
3. Define the Main() Method
CodeGen g = MyClass.Public.Static.Method(
typeof(void), "Main", typeof(string[]));
{
Operand args = g.Arg(0, "args");
}
The above corresponds to the following in C#:
public static void Main(string[] args)
{
}
Note the definition of the Operand
variable here. Operand
is one of the core classes in RunSharp
, representing a literal, argument, local variable, expression etc. It also overloads all the operators, which can be used to construct expressions much like you would in C#. The Operand
in the snippet above will represent the single argument of the main method. If you don't need to define the name of the argument (note that names of arguments are not defined in the Method()
call), you can skip this altogether and simply use g.Arg(0)
to access the argument.
4. Write the Body of the Method
g.If(args.ArrayLength() > 0);
{
g.WriteLine("Hello " + args[0] + "!");
}
g.Else();
{
g.WriteLine("Hello World!");
}
g.End();
And compare this to the C# version:
if (args.Length > 0)
{
Console.WriteLine("Hello " + args[0] + "!");
}
else
{
Console.WriteLine("Hello World!");
}
As you can see, the code is quite similar. There are some things to note - the g.WriteLine(...)
calls are a special case, and they are simply a shortcut to the longer
g.Invoke(typeof(Console), "WriteLine", ...)
you can use to call any method on any type/instance. Also, you can see the length of the arguments is retrieved by calling ArrayLength()
on the args
operand. This is similar to calling args.Property("Length")
, although it yields a bit better performance, as it's translated to the ldlen
opcode instead of accessing the property of the array object.
5. Using the Generated Code
Now that we have created a simple hello world application, you can, for example, choose to save it and run it:
ag.Save();
AppDomain.CurrentDomain.ExecuteAssembly(
"HelloWorld.exe", null, new string[] { "John" });
Or, you can just decide to complete the assembly by calling ag.Complete()
and use the generated types in any way you like (the TypeGen
class returned when defining the type is implicitly convertible to Type
).
Other Mini-Samples
In the hello world sample, only the simplest concepts were shown. You can do, of course, much more with RunSharp
. Here are some small examples of various concepts and how they are related to C#.
1. Local Variables
C#
int x = 3;
long y = x + 15;
string s;
RunSharp
Operand x = g.Local(3);
Operand y = g.Local(typeof(long), x + 15);
Operand z = g.Local(typeof(string));
Note that if the type of the variable is not specified, it automatically takes the type of the initialized expression. Also, an untyped and uninitialized variable can be created by calling g.Local()
without any parameters. Such a variable will receive the type of the first expression assigned to it.
Note that it's possible to write code such as:
Operand x = g.Local(3);
Operand y = x + 40;
However, the y
will not be a local in this case, but rather something like an 'alias' for the expression x + 40
, meaning the expression will be evaluated every time y
is used.
2. Statements
Statements are always generated using the members of the CodeGen
class. Most C# keyword-based statements have their equivalent methods in RunSharp
(i.e. Break()
for break
, Throw()
for throw
, Return()
for return
, etc.). Other than these, only assignment, method-invocation and increment/decrement statements are supported.
2.1 Assignment, Increment/Decrement
C#
a = 3;
b = a + 5;
c += 4;
b++;
RunSharp
g.Assign(a, 3);
g.Assign(b, a + 5);
g.AssignAdd(c, 4);
g.Increment(b);
Note that the assignment operator cannot be overridden in C#. That's why we need a dedicated method to perform the assignment. Also, there is a method for each compound assignment operator.
2.2. Method Invocation
C#
o.SomeMethod(arg1, arg2);
Console.WriteLine(arg1, arg2);
MyType.MyMethod(arg1, arg2);
d(arg1, arg2);
RunSharp
g.Invoke(o, "SomeMethod", arg1, arg2);
g.Invoke(typeof(Console), "WriteLine", arg1, arg2);
g.Invoke(MyType, "MyMethod", arg1, arg2);
g.InvokeDelegate(d, arg1, arg2);
Note that when invoking a method on a generated type, the typeof()
is omitted - this is because the TypeGen
converts implicitly to Type
.
3. Expressions
As already mentioned, the Operand
class allows you to construct expressions much like you would in C#. There are only a few cases when the intuitive syntax is not available.
3.1 Pre/Post-Fix Operators
C#
x = a++;
y = --b;
RunSharp
g.Assign(x, a.PostIncrement());
g.Assign(y, b.PreDecrement());
3.2 Conditional Operator (?:)
C#
x = a > 0 ? b : c;
RunSharp
g.Assign(x, (a > 0).Conditional(b, c));
You should never use the ?:
operator directly with an Operand
instance as the first operand - this will corrupt the internal state of the Operand
, as the operators true
and false
are overloaded in a way that allows the &&
and ||
operators to be used intuitively. Instead of the ?:
, you can use the op.Conditional(ifTrue, ifFalse)
syntax. You will also corrupt the state if you use an Operand
as a condition in if
, while
, for
, etc., but this is generally less likely, as you generate code using g.If()
, etc. instead.
3.3. Member Access
C#
x = o.MyMethod(arg1, arg2);
y = o.MyProperty;
o.MyProperty = z;
f = o.myField;
x = MyType.MyMethod(arg1, arg2);
y = Console.BackgroundColor;
RunSharp
g.Assign(x, o.Invoke("MyMethod", arg1, arg2));
g.Assign(y, o.Property("MyProperty"));
g.Assign(o.Property("MyProperty"), z);
g.Assign(f, o.Field("myField"));
g.Assign(x, Static.Invoke(MyType, "MyMethod", arg1, arg2);
g.Assign(y, Static.Property(typeof(Console), "BackgroundColor");
3.4. Object Creation
C#
MyType x = new MyType(arg1, arg2);
string x = new string[10, 20];
int[] x = { 1, 2, 3, 4 };
MyDelegate x = new MyDelegate(this.MyMethod);
RunSharp
Operand x = g.Local(Exp.New(MyType, arg1, arg2));
Operand x = g.Local(Exp.NewArray(typeof(string), 10, 20));
Operand x = g.Local(Exp.NewInitializedArray(typeof(int), 1, 2, 3, 4));
Operand x = g.Local(Exp.NewDelegate(MyDelegate, g.This(), "MyMethod"));
Contact Me
For any questions, ideas, suggestions, etc., don't hesitate to contact me, for example through the RunSharp
project here.
Happy Runtime
Code Generation!
History
- 2007-10-18: Original article
- 2009-08-07: Updated source code download