Udostępnij za pośrednictwem


Odbicie (C++/CLI)

Reflection umożliwia analizowanie znanych typów danych w czasie wykonywania. Odbicie 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 refleksja jest przydatną funkcją narzędzi do rozwoju oprogramowania 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 można odzyskać nazwę przestrzeni nazw, w której jest zdefiniowany typ danych, oraz nazwę klasy bazowej.

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

Uwaga

Refleksja nad .exe zbudowanego za pomocą kompilatora Microsoft C++ jest dozwolona tylko wtedy, gdy .exe został skompilowany z użyciem opcji kompilatora /clr:pure lub /clr:safe. 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 definiuje typ, na którym 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 opakowane.

// 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ą refleksji, ale technika, która może być bardziej użyteczna, to sprawdzenie lub odnalezienie prawidłowych wartości dla typów wyliczeniowych. 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 znaków, które opisują wartości 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: członkowie 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

Refleksja umożliwia również wyliczanie typów w zestawie (ang. assembly) oraz członków 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 zespołó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 funkcjonalności API odbicia statycznego xref:System.Reflection.Assembly.Load%2A?displayProperty=nameWithType w celu załadowania zestawu. 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, za pomocą funkcji Assembly.GetTypes, jest pobierana tablica obiektów Type. 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 odpytywany o składowe typu przy użyciu funkcji Type::GetMembers. Ta funkcja zwraca tablicę obiektów MethodInfo, każdy obiekt zawierający informacje o metodzie składowej, elemencie danych lub właściwości w danym 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 występują jako podstawowy składnik danych, czytany i modyfikowany przez metody get/set. Funkcje get/set są wyświetlane na tej liście jako zwykłe metody. Odbicie jest obsługiwane przez środowisko uruchomieniowe języka wspólnego, a nie przez kompilator 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 metodę TestAssembly na mscorlib, zostanie wyświetlona lista każdego typu i metody zdefiniowanej w 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);
}

Jak: Zaimplementować architekturę komponentów wtyczki przy użyciu odbicia

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 metody System.Reflection.Assembly.GetTypes. Każdy typ jest następnie sprawdzany pod kątem zgodności przy użyciu metody System.Type.IsAssignableFrom. W tym przykładzie klasy znalezione w podanym zestawie muszą być pochodną klasy Form, aby kwalifikować się jako plugin.

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 dziedziczące z klasy Form. Gdy nazwa wynikowego zestawu zostanie przekazana do pliku wykonywalnego na poprzedniej liście, każda z tych trzech klas zostanie odnaleziona i utworzona, mimo ż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);
      }
   }
};

Jak: wyliczać typy danych w zestawieniach przy użyciu odbijania

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

Mając nazwę zestawu, czy to w katalogu lokalnym, czy w GAC, poniższy kod próbuje otworzyć ten zestaw i pobierać jego opisy. W przypadku powodzenia każdy typ jest wyświetlany z członkami publicznymi.

Należy pamiętać, że System.Reflection.Assembly.Load wymaga, aby nie używać żadnego rozszerzenia pliku. W związku z tym użycie polecenia "mscorlib.dll" jako argument 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ż