Odbicie (C++/CLI)

Emocje ion umożliwia inspekcję znanych typów danych w czasie wykonywania. Emocje ion umożliwia wyliczanie typów danych w danym zestawie, a elementy członkowskie danej klasy lub typu wartości można odnaleźć. Jest to prawdą niezależnie od tego, czy typ był znany, czy przywoływał się w czasie kompilacji. Dzięki temu odbicie jest przydatną funkcją narzędzi do programowania i zarządzania kodem.

Należy pamiętać, że podana nazwa zestawu jest silną nazwą (zobacz Tworzenie i używanie zestawów o silnych nazwach), która zawiera informacje o wersji zestawu, kulturze i podpisywaniu. Należy również pamiętać, że nazwa przestrzeni nazw, w której można zdefiniować typ danych, wraz z nazwą klasy bazowej.

Najbardziej typowym sposobem uzyskiwania dostępu do funkcji odbicia jest GetType metoda . Ta metoda jest dostarczana przez System.Objectklasę , z której pochodzą wszystkie klasy zbierane przez śmieci.

Uwaga

Emocje ion w pliku exe utworzonym za pomocą kompilatora Microsoft C++ jest dozwolone tylko wtedy, gdy plik .exe jest kompilowany przy użyciu opcji /clr:pure lub /clr:safe kompilatora. Opcje kompilatora /clr:pure i /clr:safe są przestarzałe w programie Visual Studio 2015 i niedostępne w programie Visual Studio 2017. Aby uzyskać więcej informacji, zobacz /clr (kompilacja środowiska uruchomieniowego języka wspólnego).

Aby uzyskać więcej informacji, zobacz System.Reflection.

Przykład: GetType

Metoda GetType zwraca wskaźnik do Type obiektu klasy, który opisuje typ, gdy obiekt jest oparty. (Obiekt typu nie zawiera żadnych informacji specyficznych dla wystąpienia). Jednym z takich elementów jest pełna nazwa typu, który można wyświetlić w następujący sposób:

Należy pamiętać, że nazwa typu zawiera pełny zakres, w którym typ jest zdefiniowany, w tym przestrzeń nazw i że jest on wyświetlany w składni platformy .NET z kropką jako operator rozpoznawania zakresu.

// 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'

Przykład: typy wartości w polu

Typy wartości mogą być również używane z funkcją GetType , ale muszą być najpierw w polu.

// 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'

Przykład: typeid

Podobnie jak w przypadku GetType metody, operator typeid zwraca wskaźnik do obiektu Type , więc ten kod wskazuje nazwę typu System.Int32. Wyświetlanie nazw typów jest najbardziej podstawową funkcją odbicia, ale potencjalnie bardziej przydatną techniką jest sprawdzenie lub odnalezienie prawidłowych wartości dla wyliczonych typów. Można to zrobić przy użyciu statycznej funkcji Enum::GetNames , która zwraca tablicę ciągów, z których każda zawiera wartość wyliczenia w postaci tekstowej. Poniższy przykład pobiera tablicę ciągów, która opisuje wartości wyliczenia wartości dla wyliczenia Options (CLR) i wyświetla je w pętli.

Jeśli czwarta opcja zostanie dodana do wyliczenia Opcje , ten kod zgłosi nową opcję bez ponownej kompilacji, nawet jeśli wyliczenie jest zdefiniowane w osobnym zestawie.

// 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

Przykład: elementy członkowskie i właściwości GetType

Obiekt GetType obsługuje wiele elementów członkowskich i właściwości, których można użyć do zbadania typu. Ten kod pobiera i wyświetla niektóre z tych informacji:

// 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

Przykład: wyliczanie typów

Emocje ion umożliwia również wyliczanie typów w zestawie i składowych w klasach. Aby zademonstrować tę funkcję, zdefiniuj prostą klasę:

// 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; }
   }
};

Przykład: inspekcja zestawów

Jeśli powyższy kod zostanie skompilowany do biblioteki DLL o nazwie vcpp_reflection_6.dll, możesz użyć odbicia w celu sprawdzenia zawartości tego zestawu. Obejmuje to użycie funkcji interfejsu API odbicia statycznego xref:System. Emocje ion. Assembly.Load%2A?displayProperty=nameWithType, aby załadować zestaw. Ta funkcja zwraca adres obiektu zestawu , który następnie może zostać zapytany o moduły i typy w programie.

Po pomyślnym załadowaniu zestawu przez system odbicia zostanie pobrana tablica obiektów typu z funkcją Assembly.GetTypes . Każdy element tablicy zawiera informacje o innym typie, chociaż w tym przypadku zdefiniowano tylko jedną klasę. Za pomocą pętli każdy typ w tej tablicy jest odpytywane o składowe typu przy użyciu funkcji Type::GetMembers . Ta funkcja zwraca tablicę obiektów MethodInfo , każdy obiekt zawierający informacje o funkcji składowej, składowej danych lub właściwości w typie.

Należy pamiętać, że lista metod zawiera funkcje jawnie zdefiniowane w klasie TestClass i funkcje niejawnie dziedziczone z klasy System::Object . W ramach opisu na platformie .NET, a nie w składni języka Visual C++, właściwości są wyświetlane jako podstawowy element członkowski danych, do których uzyskuje dostęp funkcja get/set. Funkcje get/set są wyświetlane na tej liście jako zwykłe metody. Emocje ion jest obsługiwany za pośrednictwem środowiska uruchomieniowego języka wspólnego, a nie przez kompilator języka Microsoft C++.

Mimo że użyto tego kodu do sprawdzenia zdefiniowanego zestawu, można również użyć tego kodu do sprawdzenia zestawów platformy .NET. Jeśli na przykład zmienisz wartość TestAssembly na mscorlib, zostanie wyświetlona lista każdego typu i metody zdefiniowanej w pliku mscorlib.dll.

// 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);
}

Instrukcje: implementowanie architektury składników wtyczki przy użyciu Emocje ion

W poniższych przykładach kodu pokazano użycie odbicia w celu zaimplementowania prostej architektury "plug-in". Pierwsza lista to aplikacja, a druga to wtyczka. Aplikacja jest formularzem wielu dokumentów, który wypełnia się przy użyciu dowolnych klas opartych na formularzach znajdujących się w bibliotece DLL wtyczki dostarczonej jako argument wiersza polecenia.

Aplikacja próbuje załadować dostarczony zestaw przy użyciu System.Reflection.Assembly.Load metody . W przypadku powodzenia typy wewnątrz zestawu są wyliczane przy użyciu System.Reflection.Assembly.GetTypes metody . Każdy typ jest następnie sprawdzany pod kątem System.Type.IsAssignableFrom zgodności przy użyciu metody . W tym przykładzie klasy znalezione w podanym zestawie muszą pochodzić z Form klasy, aby kwalifikować się jako wtyczka.

Następnie klasy zgodne są tworzone za pomocą System.Activator.CreateInstance metody , która akceptuje Type jako argument i zwraca wskaźnik do nowego wystąpienia. Każde nowe wystąpienie jest następnie dołączane do formularza i wyświetlane.

Należy pamiętać, że Load metoda nie akceptuje nazw zestawów, które zawierają rozszerzenie pliku. Funkcja główna w aplikacji przycina wszystkie dostarczone rozszerzenia, więc poniższy przykład kodu działa w obu przypadkach.

Przykładowa aplikacja

Poniższy kod definiuje aplikację, która akceptuje wtyczki. Nazwa zestawu musi być podana jako pierwszy argument. Ten zestaw powinien zawierać co najmniej jeden publiczny Form typ pochodny.

// 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));
}

Przykładowe wtyczki

Poniższy kod definiuje trzy klasy pochodzące z Formklasy . Gdy nazwa wynikowego zestawu zostanie przekazana do pliku wykonywalnego na poprzedniej liście, każda z tych trzech klas zostanie odnaleziona i utworzone wystąpienie, pomimo faktu, że wszystkie były nieznane aplikacji hostingowej w czasie kompilacji.

// 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);
      }
   }
};

Instrukcje: wyliczanie typów danych w zestawach przy użyciu Emocje ion

Poniższy kod przedstawia wyliczenie typów publicznych i elementów członkowskich przy użyciu polecenia System.Reflection.

Biorąc pod uwagę nazwę zestawu, w katalogu lokalnym lub w GAC, poniższy kod próbuje otworzyć zestaw i pobrać opisy. W przypadku powodzenia każdy typ jest wyświetlany z publicznymi elementami członkowskimi.

Należy pamiętać, że System.Reflection.Assembly.Load nie jest używane żadne rozszerzenie pliku. W związku z tym użycie "mscorlib.dll" jako argumentu wiersza polecenia zakończy się niepowodzeniem, podczas gdy użycie tylko "mscorlib" spowoduje wyświetlenie typów programu .NET Framework. Jeśli nie podano nazwy zestawu, kod wykryje i zgłosi typy w bieżącym zestawie (plik EXE wynikający z tego kodu).

Przykład

// 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);
}

Zobacz też