In diesem Artikel möchte ich euch kurz aufzeigen, was man mit System.Reflection.Emit unter C# machen kann. Dies ist bei weitem kein komplettes Tutorial sondern nur eine kleine Übersicht über die Möglichkeiten mit Emit.
Was wollen wir tun?
Ziel ist es eine Klasse Person zu generieren die das Interface IPerson und IComparable implementiert. Die Klasse soll folgendermassen aussehen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public interface IPerson {
string Vorname { get; set; }
}
public class Person : IPerson, IComparable<IPerson> {
private string vorName;
public string Vorname {
get { return vorName; }
set { vorName = value; }
}
public int CompareTo(IPerson other) {
return 1;
}
} |
Sicherlich macht es nicht viel Sinn in der CompareTo Methode immer 1 zurück zu geben. Ich habe es nur so gemacht, damit das Beispiel etwas einfacher zu verstehen ist. Da das Emiten von Functioncalls auf .NET Klassen etwas schwieriger ist.
Generieren eines dynamischen Assemblies
Das untenstehende Codesegment zeigt wie man ein dynamisches Assembly erzeugt. Dazu definiert man zuerst einen AssemblyName und setzt auf dem AssemblyName das Property Name und Version entsprechend den Anforderungen (Zeile 2, 4, 6). Als nächstes holt man sich den AssemblyBuilder über die bestehende ApplicationDomain des Mainthreads (Zeile 9; dann läuft das Assembly unter der gleichen ApplicationDomain). Über den AssemblyBuilder ruft man die Methode DefineDynamicModule aus und erhält somit einen ModuleBuilder der für das Builden eines Modules zuständig ist (Zeile 11; ein Assembly kann mehrere Module enthalten).
1
2
3
4
5
6
7
8
9
10
11
| // Define the new AssemblyName
AssemblyName assName = new AssemblyName();
// Specify the assembly name (without .dll)
assName.Name = "MyEmitTest";
// Define the version
assName.Version = new Version(0, 0, 0, 1);
// Get the AssemblyBuilder for the current application domain
AssemblyBuilder assBuilder = Thread.GetDomain().DefineDynamicAssembly(assName, AssemblyBuilderAccess.RunAndSave);
// Get the ModuleBuilder over the AssemblyBuilder
ModuleBuilder modBuilder = assBuilder.DefineDynamicModule("MyFirstEmittedModule", "MyEmitTest.dll"); |
Definieren der Klasse Person
Um in dem bestehenden Module eine neue Klasse zu definieren, rufen wir die Methode DefineType des ModuleBuilders auf. Die Methode gibt ein TypeBuilder zurück, den wir brauchen um die Klasse zusammenzubauen. Damit die Klasse Person die Interfaces IPerson und IComparable kennt, muss über den TypeBuilder und die Methode AddInterfaceImplementation das entsprechende Interface dem Typ bekannt gemacht werden. Achtung dadurch implementiert der Typ Person die Methoden und Properties der Interfaces nicht automatisch, die muss auch von Hand erledigt werden (mehr dazu später).
1
2
3
4
5
6
| // Define Person class which is public
TypeBuilder personBuilder = modBuilder.DefineType("Person", TypeAttributes.Public);
// Add IPerson interface
personBuilder.AddInterfaceImplementation(typeof(IPerson));
// Add interface IComparable<IPerson>
personBuilder.AddInterfaceImplementation(typeof(IComparable<>).MakeGenericType(typeof(IPerson))); |
Definieren des Feldes vorName
Über den TypeBuilder personBuilder und die Methode DefineField kann ein neues Feld im Typ Person definiert werden. Dazu spezifiziert man den Namen des Feldes, den darunterliegenden Typ und den Fieldmodifier (private, protected, public) über das FieldAttribute. That’s the whole magic
1
2
| // Vorname Field
FieldBuilder vornameFieldBuilder = personBuilder.DefineField("vorName", typeof(string), FieldAttributes.Private); |
Definieren des Properties Vorname
Für das Definieren des Properties Vorname ist etwas mehr Arbeit nötig. Dies hat mit dem internen Aufbau der Klassen zu tun. Definiert man nämlich ein Property Vorname in C# mit einem get und set Accessor, so wird automatisch vom Compiler jeweils eine Methode get_Vorname und set_Vorname ausprogrammiert und verknüpft. Deshalb muss man nach der Definition des eigentlichen Properties über den TypeBuilder personBuilder und die Methode DefineProperty noch die Methoden get_Vorname und set_Vorname emitten. Doch Eins nach dem Anderen.
Die Methode DefineProperty des TypeBuilder personBuilder definiert den Namen des Properties (hier “Vorname”) und den Typ des Properties (hier string).
1
| PropertyBuilder vornamePropBuilder = personBuilder.DefineProperty("Vorname", PropertyAttributes.None, typeof(string), null); |
Nach der obigen Codezeile ist das Property ohne Rumpf definiert. Würde man den Typ Person bereits jetzt versuchen zur Laufzeit zu generieren, würde eine Exception geworfen, dass das Property Vorname noch nicht vollständig implementiert sei. Als erstes beginnen wir mit dem implementieren der get_Vorname Methode. Dazu ruft man auf dem TypeBuilder personBuilder die Methode DefineMethod auf und definiert die Methodenattribute (werden ver-OR-t). Da es sich bei den Methoden get_PropertyName und set_PropertyName um spezielle Methoden handelt (sogenannte Specialnames) muss das MethodAttribute SpecialName gesetzt werden!
Als nächstes holt man sich über den MethodBuilder den ILGenerator und baut über diesen die Operationen der Methode zusammen. Da dies nicht ganz trivial ist, hier ein kleiner Trick:
Der .NET Reflector von Lutze Roeder erlaubt es DLLs die mit .NET erstellt wurden einzulesen und den darunterliegenden Code zu extrahieren. Der Reflector ist erweiterbar mit einem coolen Plugin ReflectorEmitLanguage. Dieses Plugin erlaubt es den IL Code anzusehen. So kann man sich Sampleklassen mit Visual Studio bauen, diese in einer DLL speichern und schlussendlich die erzeuge DLL mit dem Reflector anzusehen. So sieht man genau wie der IL Code mit dem ILGenerator gemacht werden müsste
. Darum gehe ich jetzt nicht weiter auf den IL Code ein.
Zum Schluss muss man noch auf der Klasse mit DefineMethodOverride dem Emitter mitteilen, dass die neue Methode die Methode des Interfaces überschreibt. Sonst gibt es eine Warnung, dass die Klasse Person das Interface IPerson nicht implementiere.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // Define the 'get_Vorname' method.
MethodBuilder getVornameMethod = personBuilder.DefineMethod("get_Vorname",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
typeof(string), null);
// Generate IL code for 'get_Vorname' method.
ILGenerator methodIL = getVornameMethod.GetILGenerator();
methodIL.Emit(OpCodes.Ldarg_0);
methodIL.Emit(OpCodes.Ldfld, vornameFieldBuilder);
methodIL.Emit(OpCodes.Ret);
// Define the Set Method
vornamePropBuilder.SetGetMethod(getVornameMethod);
// Get the interface implementation
MethodInfo interfaceGetVorname = typeof(IPerson).GetMethod("get_Vorname");
// Ovveride it
personBuilder.DefineMethodOverride(getVornameMethod, interfaceGetVorname); |
Fast das gleiche Prinzip gilt für die Methode set_Vorname.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // Define the 'set_Vorname' method.
Type[] methodArgs = { typeof(string) };
MethodBuilder setVornameMethod = personBuilder.DefineMethod("set_Vorname",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
typeof(void), methodArgs);
// Generate IL code for 'set_Vorname' method.
methodIL = setVornameMethod.GetILGenerator();
methodIL.Emit(OpCodes.Ldarg_0);
methodIL.Emit(OpCodes.Ldarg_1);
methodIL.Emit(OpCodes.Stfld, vornameFieldBuilder);
methodIL.Emit(OpCodes.Ret);
vornamePropBuilder.SetSetMethod(setVornameMethod);
MethodInfo interfaceSetVorname = typeof(IPerson).GetMethod("set_Vorname");
personBuilder.DefineMethodOverride(setVornameMethod, interfaceSetVorname); |
Definieren der Methode CompareTo
CompareTo definiert die Methode des Interfaces IComparable und hat darum einen IPerson Parameter other. Dies muss ebenfalls über den MethodBuilder erstellt werden und über DefineParameter wird die Position, das Parameterattribute und der Name des Parameters festgelegt. Schlussendlich geben wir hier im Beispielcode einfach als Returnparameter eine 1 zurück, die wir über den IL Generator auf den Stack geladen haben…
1
2
3
4
5
6
7
8
9
10
| // Make compareTo Method with the Person param
Type[] compareToArgs = { typeof(IPerson) };
MethodBuilder compareToMethod = personBuilder.DefineMethod("CompareTo",
MethodAttributes.Public | MethodAttributes.Virtual, typeof(int), compareToArgs);
compareToMethod.DefineParameter(1, ParameterAttributes.None, "other");
// Generate the IL code for the compareTo Method
methodIL = compareToMethod.GetILGenerator();
methodIL.Emit(OpCodes.Ldarg_0);
methodIL.Emit(OpCodes.Ldc_I4, 1); // Pushes the value 1 (int32) to the stack
methodIL.Emit(OpCodes.Ret); |
Viel Spass
Links
MSDN System.Reflection.Emit
MSDN System.Reflection.Emit.AssemblyBuilder
MSDN System.Reflection.Emit.MethodBuilder
.NET Reflector
.NET Reflector Add-Ins