Click here to Skip to main content
5,787,682 members and growing! (18,379 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » .NET Framework » Utilities     Intermediate

RunSharp - Reflection.Emit Has Never Been Easier

By Stefan Simek

RunSharp (or Run#) is a high-level wrapper around the Reflection.Emit API, allowing you to generate code at runtime quickly and easily.
MSIL, C# 2.0, C#, Windows, .NET, .NET 2.0VS2005, Visual Studio, Dev

Posted: 17 Oct 2007
Updated: 17 Oct 2007
Views: 13,271
Bookmarked: 51 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
26 votes for this Article.
Popularity: 6.76 Rating: 4.78 out of 5
0 votes, 0.0%
1
1 vote, 3.8%
2
0 votes, 0.0%
3
2 votes, 7.7%
4
23 votes, 88.5%
5

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");
{
  // we'll continue here

} 

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

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

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

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

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:

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

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#

// 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

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

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#

// 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

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

// 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

// 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Stefan Simek



Occupation: Web Developer
Location: Slovakia Slovakia

Other popular .NET Framework articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 24 of 24 (Total in Forum: 24) (Refresh)FirstPrevNext
GeneralBug (and workaround) calling virtual functions on value typesmemberninj7:35 4 Jan '08  
GeneralRe: Bug (and workaround) calling virtual functions on value typesmemberStefan Simek12:01 6 Jan '08  
GeneralRe: Bug (and workaround) calling virtual functions on value typesmemberninj6:31 7 Jan '08  
QuestionAttributes, and Complete() methodmemberMarc Gravell21:47 23 Oct '07  
AnswerRe: Attributes, and Complete() methodmemberStefan Simek2:55 24 Oct '07  
GeneralRe: Attributes, and Complete() methodmemberMarc Gravell3:02 24 Oct '07  
GeneralWhy not going further... with Compact Framework?memberrvaquette22:30 22 Oct '07  
GeneralVery cool but...memberlaughingandliving16:25 22 Oct '07  
GeneralGeneric Support?memberDah_CN21:11 21 Oct '07  
AnswerRe: Generic Support?memberStefan Simek5:40 22 Oct '07  
GeneralRe: Generic Support?memberDah_CN19:49 22 Oct '07  
Generalgood for lots of reasonsmemberdave.dolan17:51 20 Oct '07  
GeneralNow that's....memberMarc Leger7:12 20 Oct '07  
GeneralGood stuff!memberleppie14:37 19 Oct '07  
GeneralTHis look really cool.memberJames Curran11:07 19 Oct '07  
GeneralRe: THis look really cool.memberStefan Simek4:38 20 Oct '07  
QuestionDynamicMethodmemberleeloo9992:11 19 Oct '07  
AnswerRe: DynamicMethodmemberStefan Simek4:18 19 Oct '07  
GeneralSource CodememberRicci Gian Maria20:30 18 Oct '07  
GeneralRe: Source CodememberStefan Simek20:40 18 Oct '07  
GeneralGreat Job!!!memberkemetokara13:49 18 Oct '07  
GeneralAmazingmemberr.chiodaroli9:23 18 Oct '07  
GeneralWOW :)memberD_Guidi5:40 18 Oct '07  
GeneralRe: WOW :)memberStefan Simek6:15 18 Oct '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 17 Oct 2007
Editor: Deeksha Shenoy
Copyright 2007 by Stefan Simek
Everything else Copyright © CodeProject, 1999-2009
Web11 | Advertise on the Code Project