Freigeben über


Reflexion (C++/CLI)

Die Reflektion ermöglicht die Überprüfung von bekannten Datentypen zur Laufzeit. Reflection ermöglicht die Enumeration von Datentypen in einer bestimmten Assembly, und außerdem können Mitglieder einer bestimmten Klasse oder eines bestimmten Werttyps ermittelt werden. Dies trifft unabhängig davon zu, ob der Typ zur Kompilierungszeit bekannt war bzw. auf ihn verwiesen wurde. Dies macht die Reflektion zu einer nützlichen Funktion für Entwicklungs- und Codeverwaltungstools.

Beachten Sie, dass der bereitgestellte Assemblyname der starke Name ist (siehe Erstellen und Verwenden von stark benannten Assemblys), der die Assemblyversion, -kultur und -signierungsinformationen enthält. Beachten Sie auch, dass der Name des Namespaces, in dem der Datentyp definiert ist, zusammen mit dem Namen der Basisklasse abgerufen werden kann.

Am häufigsten wird über die GetType-Methode auf Reflektionsfunktionen zugegriffen. Diese Methode wird von System.Object bereitgestellt, über die alle Garbage-Collection-Klassen abgeleitet sind.

Hinweis

Überlegungen zu einer .exe, die mit dem Microsoft C++-Compiler erstellt wurde, ist nur zulässig, wenn die .exe mit den Optionen /clr:pure oder /clr:safe compiler erstellt wird. Die Optionen "/clr:pure " und "/clr:safe compiler" sind in Visual Studio 2015 veraltet und in Visual Studio 2017 nicht verfügbar. Weitere Informationen finden Sie unter /clr (Common Language Runtime Compilation).

Weitere Informationen finden Sie unter System.Reflection.

Beispiel: GetType

Die GetType-Methode gibt einen Zeiger auf ein Type-Klassenobjekt zurück, das den Typ beschreibt, auf dem das Objekt basiert. (Die Typ-Objekt enthält keine instanzspezifischen Informationen.) Ein solches Element ist der vollständige Name des Typs, der wie folgt angezeigt werden kann:

Der Typname enthält den vollständigen Bereich, in dem der Typ definiert ist, einschließlich des Namespaces, und wird in .NET-Syntax dargestellt, wobei ein Punkt als Bereichszuweisungsoperator verwendet wird.

// vcpp_reflection.cpp
// compile with: /clr
using namespace System;
int main() {
   String ^ s = "sample string";
   Console::WriteLine("full type name of '{0}' is '{1}'", s, s->GetType());
}
full type name of 'sample string' is 'System.String'

Beispiel: Feldwerttypen

Werttypen können auch mit der GetType-Funktion verwendet werden, müssen jedoch zuerst "geboxt" werden.

// vcpp_reflection_2.cpp
// compile with: /clr
using namespace System;
int main() {
   Int32 i = 100;
   Object ^ o = i;
   Console::WriteLine("type of i = '{0}'", o->GetType());
}
type of i = 'System.Int32'

Beispiel: typeid

Wie bei der GetType Methode gibt der Typeid-Operator einen Zeiger auf ein Type-Objekt zurück, sodass dieser Code den Typnamen System.Int32 angibt. Das Anzeigen von Typnamen ist die grundlegendste Funktion der Reflektion, eine möglicherweise hilfreichere Technik besteht jedoch in der Überprüfung oder Erkennung der gültigen Werte für enumerierte Typen. Dies kann mithilfe der statischen Enum::GetNames-Funktion erfolgen, die ein Array von Zeichenfolgen zurückgibt, die jeweils einen Enumerationswert in Textform enthalten. Im folgenden Beispiel wird ein Array von Zeichenfolgen abgerufen, das die Enumerationswerte des Enums "Options" (CLR) beschreibt und diese Werte in einer Schleife anzeigt.

Wenn der Options-Enumeration eine vierte Option hinzugefügt wird, meldet dieser Code die neue Option ohne erneute Kompilierung, auch wenn die Enumeration in einer separaten Assembly definiert ist.

// vcpp_reflection_3.cpp
// compile with: /clr
using namespace System;

enum class Options {   // not a native enum
   Option1, Option2, Option3
};

int main() {
   array<String^>^ names = Enum::GetNames(Options::typeid);

   Console::WriteLine("there are {0} options in enum '{1}'",
               names->Length, Options::typeid);

   for (int i = 0 ; i < names->Length ; i++)
      Console::WriteLine("{0}: {1}", i, names[i]);

   Options o = Options::Option2;
   Console::WriteLine("value of 'o' is {0}", o);
}
there are 3 options in enum 'Options'
0: Option1
1: Option2
2: Option3
value of 'o' is Option2

Beispiel: GetType-Elemente und -Eigenschaften

Das GetType-Objekt unterstützt eine Reihe von Membern und Eigenschaften, die zur Überprüfung eines Typs verwendet werden können. Durch diesen Code werden einige dieser Informationen abgerufen und angezeigt:

// vcpp_reflection_4.cpp
// compile with: /clr
using namespace System;
int main() {
   Console::WriteLine("type information for 'String':");
   Type ^ t = String::typeid;

   String ^ assemblyName = t->Assembly->FullName;
   Console::WriteLine("assembly name: {0}", assemblyName);

   String ^ nameSpace = t->Namespace;
   Console::WriteLine("namespace: {0}", nameSpace);

   String ^ baseType = t->BaseType->FullName;
   Console::WriteLine("base type: {0}", baseType);

   bool isArray = t->IsArray;
   Console::WriteLine("is array: {0}", isArray);

   bool isClass = t->IsClass;
   Console::WriteLine("is class: {0}", isClass);
}
type information for 'String':
assembly name: mscorlib, Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
namespace: System
base type: System.Object
is array: False
is class: True

Beispiel: Aufzählung von Typen

Reflection ermöglicht auch die Auflistung von Typen innerhalb einer Assembly und von Mitgliedern innerhalb von Klassen. Um diese Funktion zu veranschaulichen, definieren Sie eine einfache Klasse:

// vcpp_reflection_5.cpp
// compile with: /clr /LD
using namespace System;
public ref class TestClass {
   int m_i;
public:
   TestClass() {}
   void SimpleTestMember1() {}
   String ^ SimpleMember2(String ^ s) { return s; }
   int TestMember(int i) { return i; }
   property int Member {
      int get() { return m_i; }
      void set(int i) { m_i = i; }
   }
};

Beispiel: Inspektion von Baugruppen

Wenn der obige Code in eine DLL mit dem Namen vcpp_reflection_6.dll kompiliert wird, können Sie mithilfe der Reflektion den Inhalt dieser Assembly überprüfen. Dazu gehört die Verwendung der Funktion xref:System.Reflection.Assembly.Load%2A?displayProperty=nameWithType zum Laden der Assembly. Diese Funktion gibt die Adresse eines Assembly-Objekts zurück, das dann nach den Modulen und Typen abgefragt werden kann.

Nachdem das Spiegelungssystem die Assembly erfolgreich geladen hat, wird ein Array von Type-Objekten mit der Assembly.GetTypes Funktion abgerufen. Jedes Arrayelement enthält Informationen zu einem anderen Typ, obwohl in diesem Fall nur eine Klasse definiert ist. Mithilfe einer Schleife wird jeder Typ in diesem Array nach den Typmembern anhand der Funktion Type::GetMembers abgefragt. Diese Funktion gibt ein Array von MethodInfo-Objekten zurück, jedes Objekt, das Informationen über die Memberfunktion, das Datenmemm oder die Eigenschaft im Typ enthält.

Beachten Sie, dass die Liste der Methoden die in TestClass explizit definierten Funktionen enthält und die Funktionen implizit von der System::Object-Klasse geerbt wurden. Bedingt dadurch, dass Eigenschaften in .NET- anstatt in Visual C++-Syntax beschrieben sind, werden sie als die zugrunde liegenden Datenmember angezeigt, auf die von den get-/set-Funktionen zugegriffen wird. Die get-/set-Funktionen werden in dieser Liste als reguläre Methoden angezeigt. Reflection wird über die Common Language Runtime unterstützt, nicht durch den Microsoft C++-Compiler.

Obwohl mit diesem Code bereits eine von Ihnen definierte Assembly überprüft wurde, können Sie ihn auch zur Überprüfung von .NET-Assemblys verwenden. Wenn Sie "TestAssembly" beispielsweise in "mscorlib" ändern, wird eine Liste aller in "mscorlib.dll" definierten Typen und Methoden angezeigt.

// vcpp_reflection_6.cpp
// compile with: /clr
using namespace System;
using namespace System::IO;
using namespace System::Reflection;
int main() {
   Assembly ^ a = nullptr;
   try {
      // load assembly -- do not use file extension
      // will look for .dll extension first
      // then .exe with the filename
      a = Assembly::Load("vcpp_reflection_5");
   }
   catch (FileNotFoundException ^ e) {
      Console::WriteLine(e->Message);
      return -1;
   }

   Console::WriteLine("assembly info:");
   Console::WriteLine(a->FullName);
   array<Type^>^ typeArray = a->GetTypes();

   Console::WriteLine("type info ({0} types):", typeArray->Length);

   int totalTypes = 0;
   int totalMembers = 0;
   for (int i = 0 ; i < typeArray->Length ; i++) {
      // retrieve array of member descriptions
      array<MemberInfo^>^ member = typeArray[i]->GetMembers();

      Console::WriteLine("  members of {0} ({1} members):",
      typeArray[i]->FullName, member->Length);
      for (int j = 0 ; j < member->Length ; j++) {
         Console::Write("       ({0})",
         member[j]->MemberType.ToString() );
         Console::Write("{0}  ", member[j]);
         Console::WriteLine("");
         totalMembers++;
      }
      totalTypes++;
   }
   Console::WriteLine("{0} total types, {1} total members",
   totalTypes, totalMembers);
}

Vorgehensweise: Implementieren einer Plug-In-Komponentenarchitektur mithilfe von Reflection

Die folgenden Codebeispiele veranschaulichen die Verwendung der Reflexion zur Implementierung einer einfachen "Plug-In"-Architektur. Die erste Auflistung ist die Anwendung, und die zweite ist das Plug-In. Die Anwendung ist ein mehrfaches Dokumentformular, das sich mit allen formularbasierten Klassen auffüllt, die in der Plug-In-DLL enthalten sind, die als Befehlszeilenargument bereitgestellt wird.

Die Anwendung versucht, die bereitgestellte Assembly mithilfe der System.Reflection.Assembly.Load Methode zu laden. Bei erfolgreicher Ausführung werden die Typen innerhalb der Assembly mithilfe der System.Reflection.Assembly.GetTypes Methode aufgezählt. Jeder Typ wird dann mithilfe der System.Type.IsAssignableFrom Methode auf Kompatibilität überprüft. In diesem Beispiel müssen Klassen, die in der bereitgestellten Assembly gefunden werden, von der Form Klasse abgeleitet werden, um als Plug-In zu qualifizieren.

Kompatible Klassen werden dann mit der System.Activator.CreateInstance Methode instanziiert, die ein Type Argument akzeptiert und einen Zeiger auf eine neue Instanz zurückgibt. Jede neue Instanz wird dann an das Formular angefügt und angezeigt.

Beachten Sie, dass die Load Methode keine Assemblynamen akzeptiert, die die Dateierweiterung enthalten. Die Hauptfunktion in der Anwendung schneidet alle bereitgestellten Erweiterungen ab, sodass das folgende Codebeispiel in beiden Fällen funktioniert.

Beispiel-App

Der folgende Code definiert die Anwendung, die Plug-Ins akzeptiert. Als erstes Argument muss ein Assemblyname angegeben werden. Diese Assembly sollte mindestens einen öffentlichen Form abgeleiteten Typ enthalten.

// plugin_application.cpp
// compile with: /clr /c
#using <system.dll>
#using <system.drawing.dll>
#using <system.windows.forms.dll>

using namespace System;
using namespace System::Windows::Forms;
using namespace System::Reflection;

ref class PluggableForm : public Form  {
public:
   PluggableForm() {}
   PluggableForm(Assembly^ plugAssembly) {
      Text = "plug-in example";
      Size = Drawing::Size(400, 400);
      IsMdiContainer = true;

      array<Type^>^ types = plugAssembly->GetTypes( );
      Type^ formType = Form::typeid;

      for (int i = 0 ; i < types->Length ; i++) {
         if (formType->IsAssignableFrom(types[i])) {
            // Create an instance given the type description.
            Form^ f = dynamic_cast<Form^> (Activator::CreateInstance(types[i]));
            if (f) {
               f->Text = types[i]->ToString();
               f->MdiParent = this;
               f->Show();
            }
         }
      }
   }
};

int main() {
   Assembly^ a = Assembly::LoadFrom("plugin_application.exe");
   Application::Run(gcnew PluggableForm(a));
}

Beispiel-Plug-Ins

Der folgende Code definiert drei Klassen, die von Form abgeleitet sind. Wenn der Name des resultierenden Assemblynamens an die ausführbare Datei im vorherigen Beispiel übergeben wird, wird jede dieser drei Klassen ermittelt und instanziiert, obwohl sie zur Kompilierungszeit der Hostanwendung alle unbekannt waren.

// plugin_assembly.cpp
// compile with: /clr /LD
#using <system.dll>
#using <system.drawing.dll>
#using <system.windows.forms.dll>

using namespace System;
using namespace System::Windows::Forms;
using namespace System::Reflection;
using namespace System::Drawing;

public ref class BlueForm : public Form {
public:
   BlueForm() {
      BackColor = Color::Blue;
   }
};

public ref class CircleForm : public Form {
protected:
   virtual void OnPaint(PaintEventArgs^ args) override {
      args->Graphics->FillEllipse(Brushes::Green, ClientRectangle);
   }
};

public ref class StarburstForm : public Form {
public:
   StarburstForm(){
      BackColor = Color::Black;
   }
protected:
   virtual void OnPaint(PaintEventArgs^ args) override {
      Pen^ p = gcnew Pen(Color::Red, 2);
      Random^ r = gcnew Random( );
      Int32 w = ClientSize.Width;
      Int32 h = ClientSize.Height;
      for (int i=0; i<100; i++) {
         float x1 = w / 2;
         float y1 = h / 2;
         float x2 = r->Next(w);
         float y2 = r->Next(h);
         args->Graphics->DrawLine(p, x1, y1, x2, y2);
      }
   }
};

Vorgehensweise: Aufzählen von Datentypen in Assemblys mithilfe von Reflection

Der folgende Code veranschaulicht die Aufzählung öffentlicher Typen und Member mithilfe von System.Reflection.

Angesichts des Namens einer Assembly, entweder im lokalen Verzeichnis oder im GAC, versucht der folgende Code, die Assembly zu öffnen und Beschreibungen abzurufen. Bei erfolgreicher Ausführung wird jeder Typ mit seinen öffentlichen Mitgliedern angezeigt.

Beachten Sie, dass bei System.Reflection.Assembly.Load keine Dateierweiterung verwendet werden darf. Daher schlägt die Verwendung von "mscorlib.dll" als Befehlszeilenargument fehl, während die Verwendung von "mscorlib" dazu führt, dass .NET Framework-Typen angezeigt werden. Wenn kein Assemblyname angegeben wird, erkennt und meldet der Code die Typen innerhalb der aktuellen Assembly (die EXE aus diesem Code).

Beispiel

// self_reflection.cpp
// compile with: /clr
using namespace System;
using namespace System::Reflection;
using namespace System::Collections;

public ref class ExampleType {
public:
   ExampleType() {}
   void Func() {}
};

int main() {
   String^ delimStr = " ";
   array<Char>^ delimiter = delimStr->ToCharArray( );
   array<String^>^ args = Environment::CommandLine->Split( delimiter );

// replace "self_reflection.exe" with an assembly from either the local
// directory or the GAC
   Assembly^ a = Assembly::LoadFrom("self_reflection.exe");
   Console::WriteLine(a);

   int count = 0;
   array<Type^>^ types = a->GetTypes();
   IEnumerator^ typeIter = types->GetEnumerator();

   while ( typeIter->MoveNext() ) {
      Type^ t = dynamic_cast<Type^>(typeIter->Current);
      Console::WriteLine("   {0}", t->ToString());

      array<MemberInfo^>^ members = t->GetMembers();
      IEnumerator^ memberIter = members->GetEnumerator();
      while ( memberIter->MoveNext() ) {
         MemberInfo^ mi = dynamic_cast<MemberInfo^>(memberIter->Current);
         Console::Write("      {0}", mi->ToString( ) );
         if (mi->MemberType == MemberTypes::Constructor)
            Console::Write("   (constructor)");

         Console::WriteLine();
      }
      count++;
   }
   Console::WriteLine("{0} types found", count);
}

Siehe auch