Laden...

[Artikel] Reflection und Metaprogrammierung

Erstellt von dN!3L vor 15 Jahren Letzter Beitrag vor 15 Jahren 34.676 Views
dN!3L Themenstarter:in
2.891 Beiträge seit 2004
vor 15 Jahren
[Artikel] Reflection und Metaprogrammierung

Jedes Programm hat eine Problemdomäne zum Gegenstand. In einem Warenwirtschaftssystem umfasst diese Domäne beispielsweise "Artikel", "Kunden" und "Bestellungen". Metaprogramme wiederum sind spezielle Programme, deren Problemdomäne Computerprogramme bilden. Sie manipulieren explizit andere Programme oder deren Repräsentation (z.B. Compiler, virtuelle Maschinen oder Entwicklungsumgebungen). Reflektive Programme sind nun spezielle Metaprogramme, die in der Lage sind, sich selbst zu beobachten (Introspection) und zu beeinflussen (Intercession).

Die .NET-Plattform, auf der C# basiert, ist in mehreren Teilen wie u. A. dem Common Type System (CTS) und der Common Language Infrastructure (CLI) spezifiziert. Bereits auf dieser Ebene wurde ein umfangreiches Metadatenkonzept integriert - was beispielsweise zur Ausführugnsverwaltung und Erstellung selbstbeschreibender Komponenten (Assemblies) genutzt wird.
Dazu wird eine Implementierung einer Klasse durch den Compiler in eine von der Programmiersprache unabhängige Form umgewandelt, welche vom Virtual Execution System (VES) ausgeführt werden kann. Diese Form wird als Common Intermediate Language bezeichnet. Die CIL-Beschreibungen der Klassen werden in Assemblies gespeichert. Bei der Ausführung eines Programms werden diese vom VES in eine interne Datenstruktur geladen. Diese Datenstruktur ist implementierungsspezifisch.
Mittels der .NET Reflection API kann während der Laufzeit auf Informationen der internen Repräsentationen zugegriffen werden (jedoch nur lesend). Wie das genau geschieht, soll in den folgenden Abschnitten erläutert werden:1.Auslesen von Typinformationen 1.Objekte erzeugen und manipulieren 1.Besonderheiten bei der Verwendung von Generika 1.Selbst definierte Metadaten 1.Typen dynamisch zur Laufzeit erzeugen 1.Dynamische Methoden

Im Artikel habe ich versucht, einen umfassenden Überblick zu geben und (im Beispielcode) möglichst alle Möglichkeiten zumindest einmal anzuschneiden. Bei Fragen zur API bitte auch die verlinkten (:rtfm: Doku-)Seiten durchlesen.

Bevor du nach dem Durchlesen aber gleich anfängst, deine Programme mit Reflection vollzupflastern, sei noch folgendes gesagt: (Reflection ist eine mächtige Waffe, aber bei unsachgemäßen Gebrauch kann man sich aber auch schnell in den eigenen Fuß schießen!


[b]Überlege dir, ob du im jeweiligen Fall wirklich Reflection benutzen musst![/b]
[list]
[*]Oft wird z.B. Reflection in normalen Businessklassen benutzt. Das ist aber fast nie angebracht. Reflection ist eher für Infrastrukturklassen gedacht - so z.B. für Serialisierung und das Auslesen von Attributen aus Businessklassen (z.B. Zwecks Darstellung in der GUI).
[*]Mittels Reflection kann auch auf nicht öffentliche Member zugegriffen werden. Dass man "normalerweise" auf diese Member NICHT zugreifen kann, hat aber immer auch einen Grund. Solltest du also keinen wirklich triftigen Grund haben, gegen diese Kapselung absichtlich zu verstoßen, dann lass es!
[/list]
Durch nicht mehr durchführbare Compilerprüfungen steht man zudem vor folgenden Herausforderungen:
[list]
[*]Typensicherheit gibt's nicht mehr.
[*]Um Lesbarkeiten muss man sich selbst kümmern.
[*]Performanz wird ein großes Thema. 
[*]Es können einem viele und unverständliche Exceptions um die Ohren fliegen.
[/list]

Bevor es richtig losgeht, möchte ich mich schon einmal zum einem bei herbivore und den Powerusern (für ihre Kritik und Anregungen beim Review des Artikels) und zum anderen bei Robert Wierschke (ohne den es bestimmte Teile des Artikels in dieser Form nicht geben würde) bedanken. 🙂

Reflection-API im Detail
In der Einleitung wurden ja schon die "Infoobjekte" genannt, mit denen man auf Typinformationen (nur lesend!) zugreifen kann: Die Wurzel der gesamten Infoobjekt-Hierarchie ist die Klasse Type. Diese stellt ein Infoobjekt für Typdeklarationen dar: Klassentypen, Schnittstellentypen, Arraytypen, Werttypen, Enumerationstypen, Typparameter, generische Typdefinitionen und offen oder geschlossen konstruierte generische Typen. Durch u.a. folgende Möglichkeiten kommt man an ein Type-Objekt:*Hat man ein Exemplar ("Instanz") einer Klasse: Mit der GetType()-Methode. *Ausgehend von einem Typen(namen) kann der typeof-Operator verwendet werden. *Wenn man den Namen des Typs kennt (also als Zeichenkette vorliegen hat), kann die Methode Type.GetType("TypName") verwendet werden. *Typen sind in Modulen bzw. Assemblies definiert, dementsprechend lassen sich die beinhalteten Typenobjekte auch über Methoden des Module- bzw. Assembly-Infoobjekts erzeugen.


// Type-Objekt für den Typen "System.String" ermitteln
Type type1 = "abc".GetType();
Type type2 = typeof(String);
Type type3 = Type.GetType("System.String");

// Type(n)-Objekt(e) aus einer Assembly laden
Assembly assembly = Assembly.Load(...);
Type[] types = assembly.GetTypes();
Type type4 = assembly.GetType("CustomType");

Auslesen von Typinformationen
Ausgehend von einem Type-Objekt sind alle zu diesem Typ bzw. zur repräsentierten Klasse gehörenden Infoobjekte erreichbar. Die Reflection-API verwendet dabei eine einheitliche Nomenklatur:*Infoobjekte werden durch Klassen beschrieben, deren Name mit Info endet, Beispielsweise MethodInfo, MemberInfo, FieldInfo, PropertyInfo, ParameterInfo oder ConstructorInfo.

  • Zur Navigation zwischen den Infoobjekten gibt es Methoden der Form
    Gets(). Diese liefent alle entsprechenden Infoobjekte, beispielsweise GetFields(), GetMethods() oder GetParameters().
    (_Hinweis: Bei der zurückgegebenen Auflistung kann man NICHT von einer bestimmten Sortierung ausgehen. Wenn man die gleiche Get
    s()-Methode zweimal hintereinander aufruft, kann das Ergebnis unterschiedlich sortiert sein!_)
  • Get***("Name") liefert das Infoobjekt mit dem gegebenen Namen, beispielsweise GetMethod("Main").

Mit Hilfe von so genannten Bindungs-Flags können zusätzlich die durch die jeweilige Get*-Methode zurückgelieferten Infoobjekte genauer spezifiziert werden. Dadurch ist es zudem auch möglich, an nicht öffentliche Member zu gelangen.


Bei der Verwendung von [url]BindingFlags[/url] müssen [b]immer sowohl[/b] Zugriffsmodifizier ([tt]BindingFlags.Public[/tt], [tt]BindingFlags.NonPublic[/tt], etc.) [b]als auch[/b] [tt]BindingFlags.Static[/tt] und/oder [tt]BindingFlags.Instance[/tt] angeben werden! Sonst erhält man nur leere Listen bzw. [tt]null[/tt] zurück.

Mithilfe der jeweiligen Infoobjekte können nun bestimmte Informationen abgerufen werden, zum Beispiel:

Type type = typeof(string);
Console.WriteLine(type.FullName);
Console.WriteLine(type.Attributes);
Console.WriteLine(type.Assembly.CodeBase);

// Basistypen
Console.WriteLine("Vererbungsbaum:");
Type baseType = type.BaseType;
while (baseType!=null)
{
	Console.WriteLine(baseType.FullName);
	baseType = baseType.BaseType;
}

// implementierte Interfaces
Console.WriteLine("Interfaces:");
foreach (Type interfaceType in type.GetInterfaces())
	Console.WriteLine(interfaceType.FullName);

// alle Member des Typs durchlaufen (auch die privaten)
foreach (MemberInfo memberInfo in type.GetMembers(BindingFlags.Instance|BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic))
{
	if (memberInfo is ConstructorInfo)
	{
		// Informationen zum Konstruktor
		ConstructorInfo constructorInfo = (ConstructorInfo)memberInfo;
		Console.WriteLine("Konstruktor:");
		Console.WriteLine(constructorInfo.Name);
		Console.WriteLine(constructorInfo.Attributes);
		foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters())
			Console.WriteLine(parameterInfo.Name+" ("+parameterInfo.ParameterType.FullName+")");
	}
	else if (memberInfo is FieldInfo)
	{
		// Informationen zum Feld
		FieldInfo fieldInfo = (FieldInfo)memberInfo;
		Console.WriteLine("Feld:");
		Console.WriteLine(fieldInfo.Name+" ("+fieldInfo.FieldType.FullName+")");
		Console.WriteLine(fieldInfo.Attributes);
	}
	else if (memberInfo is MethodInfo)
	{
		// Informationen zur Methode
		MethodInfo methodInfo = (MethodInfo)memberInfo;
		Console.WriteLine("Methode:");
		Console.WriteLine(methodInfo.Name+" ("+methodInfo.ReturnType.FullName+")");
		Console.WriteLine(methodInfo.Attributes);
		foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
			Console.WriteLine(parameterInfo.Name+" ("+parameterInfo.ParameterType.FullName+")");
	}
	else if (memberInfo is PropertyInfo)
	{
		// Informationen zum Property
		PropertyInfo propertyInfo = (PropertyInfo)memberInfo;
		Console.WriteLine("Eigenschaft:");
		Console.WriteLine(propertyInfo.Name+" ("+propertyInfo.PropertyType.FullName+")");
		Console.WriteLine(propertyInfo.Attributes);
		// ein Property muss nicht zwingend Getter UND Setter haben!
		if (propertyInfo.CanRead)
			Console.WriteLine(propertyInfo.GetGetMethod(true).Name);
		if (propertyInfo.CanWrite)
			Console.WriteLine(propertyInfo.GetSetMethod(true).Name);
	}
	Console.WriteLine();
}

Nicht auf die Idee kommen, Reflection zu benutzen, um auf durchnummerierte Variablen oder ähnliches zuzugreifen. Mehr dazu unter [url][FAQ] Variablennamen zur Laufzeit zusammensetzen[/url]

Objekte erzeugen und manipulieren
Grundlage zum Erzeugen neuer Objekte ist wieder ein Type-Objekt. Hat man dieses, kann man mit der Methode Activator.CreateInstance(...) ein neues Exemplar dieses Typs erzeugen.

Uri uri = (Uri)Activator.CreateInstance(typeof(Uri),"http://www.myCSharp.de");
StringBuilder stringBuilder = Activator.CreateInstance<StringBuilder>();

Tipp für "Profis": Man kann auch neue Exemplare erzeugen, OHNE dabei den Konstruktor aufzurufen (hilfreich z.B. bei Deserialisierungen): FormatterServices.GetUninitializedObject(...).

Über die entsprechenden Infoobjekte können Objekte inspiziert und auch manipuliert werden. Da ein Infoobjekt nicht einem speziellen Objekt zugeordnet ist, muss hierbei immer eine Referenz auf das jeweilige Objekt übergeben werden (sofern der Member nicht statisch ist). Um beispielsweise ein Feld eines Objekts zu ändern, muss das entsprechende FieldInfo ermittelt werden und die Methode GetValue aufgerufen werden. Das Schreiben eines Feldes geschieht analog mittels SetValue. Ebenso bei PropertyInfos, wobei man aber hier beachten muss, dass ein Property nicht immer schreib- oder lesbar ist.
Das Aufrufen einer Methode gelingt über das entsprechende MethodInfo unter Angabe der Objektreferenz (falls nicht statisch) und ggf. der Parameter des Methodenaufrufs mithilfe der Methode Invoke.
(Hinweis: Beim Aufruf/Lesen/Schreiben von statischen Membern übergibt man statt einem existierenden Exemplar einfach null.)


private class Test
{
	public int Field;
	private static string property { get; set; }
	public void Method() { Console.WriteLine("Hello!"); }
	public int Method(int value) { return value; }
}

[...]

Test test = new Test();
Type type = test.GetType();

// Feld schreiben und lesen
FieldInfo fieldInfo = type.GetField("Field");
fieldInfo.SetValue(test,42);
Console.WriteLine(fieldInfo.GetValue(test));

// privates statisches Property schreiben und auslesen
PropertyInfo propertyInfo = type.GetProperty("property",BindingFlags.NonPublic|BindingFlags.Static);
propertyInfo.SetValue(null,"23",null);
Console.WriteLine(propertyInfo.GetValue(null,null));

// Methoden ausführen 
// Achtung: Parametertypen müssen wegen Mehrdeutigkeit mit übergeben werden
MethodInfo methodInfo1 = type.GetMethod("Method",new Type[]{});
methodInfo1.Invoke(test,null);
MethodInfo methodInfo2 = type.GetMethod("Method",new[] { typeof (int) });
Console.WriteLine(methodInfo2.Invoke(test,new object[]{ 123 }));

Kapselung hat seinen Grund! Solltest du wirklich auf NICHT öffentliche Member zugreifen wollen, solltest du dafür einen noch wichtigeren Grund haben!


Nicht auf die Idee kommen, als Funktionszeigerersatz den Namen einer Methode oder ein [tt]MethodInfo[/tt] zu übergeben! Dafür gibt es [url]Delegates[/url].

Besonderheiten bei der Verwendung von Generika
Mit Generika kann bei der Struktur und dem Verhalten eines Typs von bestimmten Datentypen abstrahiert werden. Zum Beispiel sind Zugriffsmethoden von Listen auf einen konkreten Datentypen angepasst. Durch Verwendung von generischen Typen bzw. Methoden kann eine allgemeine Implementierung realisiert werden, die erst später auf einen bestimmten Datentypen angepasst wird.
Die generischen Typargumente können für einen Typen oder eine Methode über die Methode GetGenericArguments() des entsprechenden Infoobjekts ermittelt werden. Ebenso haben die Infoobjekte Methoden, um zu ermitteln, ob generische Typargumente verwendet wurden (z.B. IsGenericParameter, IsGenericType oder IsGenericTypeDefinition).
Mit der Methode GetGenericTypeDefinition() ist es auch möglich, aus einem Typen mit generischen Typargumenten wieder an einen generischen Typen zu gelangen. Ebenso erlaubt der typeof-Operator, dass man erst gar kein generisches Typargument angibt.
Beim Aufrufen von generischen Methoden muss zudem beachtet werden, dass zunächst der generische Typparameter festgelegt werden muss, bevor die Methode aufgerufen werden kann.

public class Test<T>
{
	public T Field;
	public G Method<G>(G param) { return param; }
} 

[...]

// generisches Typargument ist "System.String"
Type type1 = typeof(Test<string>);
Console.WriteLine(type1.GetGenericArguments()[0].FullName);
Console.WriteLine(type1.GetField("Field").FieldType);

// type2 und type3 sind dann gleich
Type type2 = type1.GetGenericTypeDefinition();  // "Entfernen" eines gebundenen generischen Typarguments
Type type3 = typeof(Test<>);					// gar kein generisches Typargument angeben

// Aufrufen einer generischen Methode
Test<int> test = new Test<int>();
MethodInfo methodInfo = test.GetType().GetMethod("Method");
methodInfo = methodInfo.MakeGenericMethod(typeof(int));
Console.WriteLine(methodInfo.Invoke(test,new object[] { 123 }));

Oft wird versucht, nicht vorhandene/nicht passende generische Typparameter durch Benutzung von Reflection zu umgehen (z.B. eine [url]abstrakte Klasse benutzen, obwohl der Typparameter abstrakt ist[/url]). Sowas geht auch mit Reflection nicht, also vergesst es!

Selbst definierte Metadaten
Wie oben gezeigt, werden in .NET Metadaten zur Beschreibung von Typen verwendet. Es ist möglich, diese Beschreibung durch weitere Metadaten zu ergänzen. Solche Metadaten werden in .NET als Attribute bezeichnet. Die .NET Basisklassenbibliothek verfügt bereits über eine Anzahl solcher Attribute (z.B.: SerialalizableAttribute, WebServiceAttribute, XmlElementAttribute-Klasse), erlaubt aber auch die Definition eigener Attribute.
Attribute können mit der Methode GetCustomAttributes(...) des entsprechenden Infoobjekts ermittelt werden, wobei Attribute vom Type-Objekt, über die ganzen MemberInfos bis hin zum ParameterInfo ausgelesen werden können.


// Typ, dessen Properties Attribute besitzen
public class Foo
{
	[XmlElement("foo")]
	public string Bar { get; set; }

	[XmlElement("bar")]
	public string Baz { get; set; }
}

[...]

foreach (PropertyInfo propertyInfo in typeof(Foo).GetProperties())
{
   // das XmlElement-Attribut des Properties auslesen (falls vorhanden)
   var xmlElementAttribute = (XmlElementAttribute) propertyInfo.GetCustomAttributes(typeof(XmlElementAttribute), false).SingleOrDefault();
   if (xmlElementAttribute!=null)
   {
      string name = xmlElementAttribute.ElementName;
   }
}

Weiterführende Links dazu:* [Artikel] Attribute zur Prüfung von Properties verwenden

.

Typen dynamisch zur Laufzeit erzeugen
Bisher wurde gezeigt, wie die Struktur von Objekten zur Laufzeit ausgelesen werden kann und wie man Objekte manipuliert. Diese Manipulationen bezogen sich aber nur auf das Ändern des Zustandes eines bereits bestehenden Objektes. Dessen Struktur und Verhalten zu verändern ist mit diesen Mitteln jedoch nicht möglich. Dynamische Programmiersprachen wie Smalltalk oder Ruby erlauben zum Beispiel das Hinzufügen und Löschen von Methoden oder das Ändern derer Implementierungen. Da C# ein statisches Typsystem besitzt, ist dies hier nicht möglich – was aber in vielen Fällen dadurch kompensiert werden kann, dass man neue Typen dynamisch zur Laufzeit erstellt.

Das .NET-Framework bietet zwei verschiedene Alternativen, um eigene Typen zur Laufzeit zu erstellen:1.Das Erzeugen einer Assembly durch Kompilieren von Quelltext mithilfe einer ICodeCompiler-Implementierung für eine konkrete .NET-Sprache, die den benutzerdefinierten Typ enthält. Aber das ist was für Anfänger, wir nehmen Methode 2: 😁

1.Mit Hilfe von Funktionalitäten aus dem Namensraum System.Reflection.Emit wird MSIL-Code direkt erzeugt. Dabei werden assemblerähnliche MSIL-Befehle zusammengestellt und mit Metainformationen kombiniert.

System.Reflection.Emit
Ein wesentlicher Bestandteil des System.Reflection.Emit-Namensraums sind verschiedene Builderobjekte. Diese dienen der Konstruktion der verschiedenen Komponenten und können auch als Referenz auf jene benutzt werden. Die Benutzung als Referenz wird später erläutert, zunächst soll die Konstruktion näher betrachtet werden:
Als Grundlage für die dynamische Typerzeugung wird ein TypeBuilder-Objekt benötigt. Typen sind laut CLI in Modulen enthalten, welche sich wiederum in einer Assembly befinden. Daher muss zunächst ein AssemblyBuilder-Objekt erstellt werden, mit Hilfe dessen dann ein ModuleBuilder-Objekt erzeugt werden kann.
Innerhalb des vom ModuleBuilder-Objekt repräsentierten Moduls kann anschließend der zu erzeugende Typ erstellt werden. Als Parameter können die grundlegenden Merkmale des neuen Typs angegeben werden – u. A. der Name, Zugriffsmodifizierer oder eine Basisklasse.

AssemblyName assemblyName = new AssemblyName("AssemblyName");
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("ModuleName");

TypeBuilder typeBuilder = moduleBuilder.DefineType("TypeName",TypeAttributes.Public|TypeAttributes.Class);

Mithilfe des TypeBuilders können nun weitere Builder erstellt werden, so z.B. FieldBuilder, PropertyBuilder, MethodBuilder oder ConstructorBuilder.

public interface Interface
{
	string One { get; set; }
	int Two { get; set; }
}

[...]

// Schnittstelle implementieren
typeBuilder.AddInterfaceImplementation(typeof(Interface));
// alle Properties aus dieser Schnittstelle implementieren
foreach (PropertyInfo propertyInfo in typeof(Interface).GetProperties())
{
	PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyInfo.Name,PropertyAttributes.HasDefault,propertyInfo.PropertyType,null);

	FieldBuilder fieldBuilder = typeBuilder.DefineField(propertyInfo.Name,propertyInfo.PropertyType,FieldAttributes.Private);
	MethodAttributes methodAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual | MethodAttributes.HideBySig;

	// Setter anlegen
	MethodBuilder setMethodBuilder = typeBuilder.DefineMethod("set_"+propertyInfo.Name,methodAttributes,null,new Type[] { propertyInfo.PropertyType });
	typeBuilder.DefineMethodOverride(setMethodBuilder,propertyInfo.GetSetMethod());
	[...]
	propertyBuilder.SetSetMethod(setMethodBuilder);

	// Getter anlegen
	MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_"+propertyInfo.Name,methodAttributes,propertyInfo.PropertyType,null);
	typeBuilder.DefineMethodOverride(getMethodBuilder,propertyInfo.GetGetMethod());
	[...]
	propertyBuilder.SetGetMethod(getMethodBuilder);
}

Bis jetzt haben wir die Methode aber erstmal nur deklariert – der interessante Teil, das zugehörige Verhalten zu implementieren, fehlt noch: Dafür muss der für die Implementierung benötigte MSIL-Code muss zusammengestellt werden. Zu diesem Zweck besitzt das MethodBuilder-Objekt ein ILGenerator-Objekt. Durch einen Aufruf der Methode Emit(...) wird der als Parameter übergebene MSIL-Befehl an die Liste der bereits zusammengestellten Befehle der Methodenimplementierung angehängt. Ein MSIL-Befehl wird dabei durch ein Element der Auflistung System.Reflection.Emit.OpCodes repräsentiert.
Für bestimmte Befehle werden jedoch auch Metadaten als zusätzlicher Parameter benötigt – so zum Beispiel bei Verweisen (Methodenaufrufe, Feldzugriffe, etc.). Dabei kann entweder ein Builderobjekt verwendet werden – sofern dies vorhanden ist – oder aber ein entsprechendes Infoobjekt, das mittels Reflection ermittelt werden kann.

// Aufruf einer Methode. Dabei wird der Wert eines Feldes als Parameter übergeben
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Stfld,fieldBuilder);
ilGenerator.Emit(OpCodes.Ldarg_1);
MethodInfo mi = typeof(AType).GetMethod("SayHello");
ilGenerator.Emit(OpCodes.Callvirt,mi);
ilGenerator.Emit(OpCodes.Pop);
ilGenerator.Emit(OpCodes.Ret);

Der konkrete Typ kann schließlich durch einen Aufruf der Methode typeBuilder.CreateType() erzeugt werden, welche ein Type-Objekt zurück gibt.


Bei diesem Verfahren, Typen dynamisch zur Laufzeit zu erzeugen, muss jedoch beachtet werden, dass wenige Überprüfungen der generierten MSIL-Befehle stattfinden. Es wird zwar geprüft, ob alle Methoden eines Interfaces auch wirklich implementiert wurden, für die eigentlichen Implementierungen können MSIL-Befehle aber beliebig zusammengestellt werden. [b]Fehlerhafte MSIL-Codestücke werden dadurch erst bei der Ausführung der generierten Methode(!) bemerkt.[/b]

Dynamische Methoden
Mit der DynamicMethod-Klasse kann eine Methode zur Laufzeit generiert und ausführt werden, ohne eine dynamische Assembly oder einen dynamischen Typ erstellen zu müssen, die bzw. der die Methode enthält.
Die Methode wird dann genau wie oben beschrieben implementiert; zum Aufrufen der Methode erhält man dann ein Delegate zurück.

public delegate string MethodDelegate(int param);

[...]

DynamicMethod dynamicMethod = new DynamicMethod("Method",typeof(string),new Type[] { typeof(int) });
ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
[...]
MethodDelegate methodDelegate = (MethodDelegate)dynamicMethod.CreateDelegate(typeof(MethodDelegate));
string test = methodDelegate(123);

**weiterführende Links dazu:**1. DynamicFieldAccessor statt Reflection - schneller dynamischer Zugriff auf Felder/Properties

  1. [Artikel] .NET Reflector (der Reflector ist ein Muss!)
  2. Reflection.Emit als zusätzliche "Sprache" für den .NET Reflector