myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
   » Plugin für Firefox
   » Plugin für IE
   » Gadget für Windows
» Regeln
» Wie poste ich richtig?
» Datenschutzerklärung
» wbb-FAQ

Mitglieder
» Liste / Suche
» Stadt / Anleitung dazu
» Wer ist wo online?

Angebote
» ASP.NET Webspace
» Bücher
» Zeitschriften
   » dot.net magazin
» Accessoires

Ressourcen
» .NET-Glossar
» guide to C#
» openbook: Visual C#
» openbook: OO
» .NET BlogBook
» MSDN Webcasts
» Search.Net

Team
» Kontakt
» Übersicht
» Wir über uns
» Bankverbindung
» Impressum

» Unsere MiniCity
MiniCity
» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Knowledge Base » Artikel » [Artikel] Reflection und Metaprogrammierung
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | An Freund senden | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

[Artikel] Reflection und Metaprogrammierung

 
Beiträge zu diesem Thema Autor Datum
 [Artikel] Reflection und Metaprogrammierung dN!3L 28.02.2009 17:51

Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
dN!3L dN!3L ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2985.png


Dabei seit: 13.08.2004
Beiträge: 2.885


dN!3L ist offline

[Artikel] Reflection und Metaprogrammierung

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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
  2. Objekte erzeugen und manipulieren
  3. Besonderheiten bei der Verwendung von Generika
  4. Selbst definierte Metadaten
  5. Typen dynamisch zur Laufzeit erzeugen
  6. 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 (Bitte schau in die SDK-/MSDN-Doku 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!

Achtung!
Überlege dir, ob du im jeweiligen Fall wirklich Reflection benutzen musst!
  • 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!
Durch nicht mehr durchführbare Compilerprüfungen steht man zudem vor folgenden Herausforderungen:
  • 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.

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.
C#-Code (Type-Objekt ermitteln):
// 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
    Get***s(). 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.

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

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

C#-Code (Auslesen von Typinformationen):
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();
}

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

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.

C#-Code (Objekte anhand von Typinformationen 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.)

C#-Code (Objekte anhand von Typinformationen manipulieren):
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 }));

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

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

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.

C#-Code (Generika und Typinformationen):
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 }));

Achtung!
Oft wird versucht, nicht vorhandene/nicht passende generische Typparameter durch Benutzung von Reflection zu umgehen (z.B. eine  abstrakte Klasse benutzen, obwohl der Typparameter abstrakt ist). 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.

C#-Code (Auslesen von Attributen):
// 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:.


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: großes Grinsen
  2. 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.

C#-Code (Builder-Objekte erstellen):
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.

C#-Code (Mittels TypeBuilder dynamisch Typen erstellen):
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.

C#-Code (Mithilfe des ILGenerators einen Methodenrumpf implementieren):
// 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.

Achtung!
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. Fehlerhafte MSIL-Codestücke werden dadurch erst bei der Ausführung der generierten Methode(!) bemerkt.

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.

C#-Code (Dynamische Methoden erstellen):
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:

Dieser Beitrag wurde 6 mal editiert, zum letzten Mal von dN!3L am 28.10.2011 09:02.

28.02.2009 17:51 Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 5 Jahre.
Der letzte Beitrag ist älter als 5 Jahre.
Antwort erstellen


© Copyright 2003-2014 myCSharp.de-Team. Alle Rechte vorbehalten. 16.09.2014 17:24