Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / MSIL

RunSharp - Reflection.Emit Has Never Been Easier

4.92/5 (48 votes)
10 Aug 2009MIT5 min read 1   510  
RunSharp (or Run#) is a high-level wrapper around the Reflection.Emit API, allowing you to generate code at runtime quickly and easily.

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

C#
AssemblyGen ag = new AssemblyGen("HelloWorld.exe");

This call simply creates an AssemblyBuilder and ModuleBuilder under the hood.

2. Define Your First Class

C#
TypeGen MyClass = ag.Public.Class("MyClass");
{
  // we'll continue here
} 

As you can see, this is very similar to what you would write in C#, e.g.:

C#
public class MyClass
{
  // we'll continue here
}

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

C#
CodeGen g = MyClass.Public.Static.Method(
  typeof(void), "Main", typeof(string[]));
{
  Operand args = g.Arg(0, "args");
  // we'll continue here
}

The above corresponds to the following in C#:

C#
public static void Main(string[] args)
{
  // we'll continue here
}

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

C#
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:

C#
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

C#
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:

C#
// this will save the generated assembly
// to the file provided at construction time
ag.Save();

// execute the generated assembly
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#

C#
int x = 3;
long y = x + 15;
string s;

RunSharp

C#
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:

C#
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#

C#
a = 3;
b = a + 5;
c += 4;
b++;

RunSharp

C#
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#

C#
// instance method
o.SomeMethod(arg1, arg2);
// static method of a system type
Console.WriteLine(arg1, arg2);
// static method of a custom type
MyType.MyMethod(arg1, arg2);
// delegate
d(arg1, arg2);

RunSharp

C#
// instance method
g.Invoke(o, "SomeMethod", arg1, arg2);
// static method of a system type
g.Invoke(typeof(Console), "WriteLine", arg1, arg2);
// static method of a custom type
g.Invoke(MyType, "MyMethod", arg1, arg2);
// delegate
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#

C#
x = a++;
y = --b;

RunSharp

C#
g.Assign(x, a.PostIncrement());
g.Assign(y, b.PreDecrement());
3.2 Conditional Operator (?:)

C#

C#
x = a > 0 ? b : c;

RunSharp

C#
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#

C#
// instance member access
x = o.MyMethod(arg1, arg2);
y = o.MyProperty;
o.MyProperty = z;
f = o.myField;

// static member access 
x = MyType.MyMethod(arg1, arg2);
y = Console.BackgroundColor;

RunSharp

C#
// instance member access
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"));

// static member access
g.Assign(x, Static.Invoke(MyType, "MyMethod", arg1, arg2);
g.Assign(y, Static.Property(typeof(Console), "BackgroundColor");
3.4. Object Creation

C#

C#
// instance creation
MyType x = new MyType(arg1, arg2);
// array creation
string x = new string[10, 20];
// initialized array creation
int[] x = { 1, 2, 3, 4 };
// delegate creation
MyDelegate x = new MyDelegate(this.MyMethod); 

RunSharp

C#
// instance creation
Operand x = g.Local(Exp.New(MyType, arg1, arg2));
// array creation
Operand x = g.Local(Exp.NewArray(typeof(string), 10, 20));
// initialized array creation
Operand x = g.Local(Exp.NewInitializedArray(typeof(int), 1, 2, 3, 4));
// delegate creation
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

License

This article, along with any associated source code and files, is licensed under The MIT License