Reflektion (C++/CLI)

Die Reflektion ermöglicht die Überprüfung von bekannten Datentypen zur Laufzeit. Sie ermöglicht die Enumeration von Datentypen in einer bestimmten Assembly. Darüber hinaus können Member einer bestimmten Klasse oder eines bestimmten Werttyps erkannt 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 Assemblys mit starkem Namen), 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, von der alle garbage-collection-Klassen abgeleitet sind, bereitgestellt.

Hinweis

Spiegelung einer mit dem Microsoft C++-Compiler erstellten EXE-Datei ist nur zulässig, wenn die EXE-Datei 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 Das Type-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 Namespace und Darstellung in .NET-Syntax, wobei ein Punkt als Bereichsauflösungsoperator 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 geschachtelt 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 Werteaufzählungswerte für die Enumeration "Options " (CLR) beschreibt und diese 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

Die Reflektion unterstützt auch die Enumeration von Typen innerhalb einer Assembly und die von Membern 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. Bei Verwendung einer Schleife wird jeder Typ in diesem Array anhand der Funktion "Type::GetMembers " über die Typmember 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 Standard-Funktion 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. Wenn der Name des resultierenden Assemblynamens an die ausführbare Datei in der vorherigen Auflistung übergeben wird, werden jede dieser drei Klassen ermittelt und instanziiert, obwohl sie alle unbekannt waren, die Hostanwendung zur Kompilierungszeit.

// 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 System.Reflection.Assembly.Load keine Dateierweiterung verwendet wird. Daher schlägt die Verwendung von "mscorlib.dll" als Befehlszeilenargument fehl, während nur "mscorlib" verwendet wird, wird die Anzeige der .NET Framework-Typen verursacht. 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