Поделиться через


Отражение (C++/CLI)

Отражение позволяет проверять известные типы данных во время выполнения. Отражение позволяет перечислить типы данных в данной сборке, а элементы заданного класса или типа значений можно обнаружить. Это верно независимо от того, был ли тип известен или ссылался во время компиляции. Это делает отражение полезной функцией для средств разработки и управления кодом.

Обратите внимание, что указанное имя сборки — это строгое имя (см. статью "Создание и использование сборок с строгим именем"), включающее версию сборки, язык и региональные параметры и сведения о подписи. Обратите внимание также, что имя пространства имен, в котором определен тип данных, можно получить вместе с именем базового класса.

Наиболее распространенным способом доступа к функциям отражения является GetType метод. Этот метод предоставляется методом System.Object, из которого наследуются все собранные мусором классы.

Примечание.

Отражение .exe, созданного с помощью компилятора Microsoft C++, допускается только в том случае, если .exe построен с помощью параметров компилятора /clr:pure или /clr:safe компилятора. Параметры компилятора /clr:pure и /clr:safe компилятора устарели в Visual Studio 2015 и недоступны в Visual Studio 2017. Дополнительные сведения см. в разделе /clr (компиляция среды CLR).

Дополнительные сведения см. в разделе System.Reflection.

Пример: GetType

Метод GetType возвращает указатель на Type объект класса, описывающий тип при создании объекта. (The Объект Type не содержит никаких сведений, относящихся к экземпляру.) Один из таких элементов — полное имя типа, которое можно отобразить следующим образом:

Обратите внимание, что имя типа включает полную область, в которой определен тип, включая пространство имен, и что он отображается в синтаксисе .NET, с точкой в качестве оператора разрешения области.

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

Пример: типы полей значений

Типы значений GetType также можно использовать с функцией, но сначала они должны быть заполнены.

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

Пример: typeid

Как и в GetType случае с методом, оператор typeid возвращает указатель на объект Type , поэтому этот код указывает имя типа System.Int32. Отображение имен типов является наиболее базовой функцией отражения, но потенциально более полезный способ заключается в проверке или обнаружении допустимых значений для перечисленных типов. Это можно сделать с помощью статической функции Enum::GetNames , которая возвращает массив строк, каждый из которых содержит значение перечисления в текстовой форме. В следующем примере извлекается массив строк, описывающий значения перечисления значений для перечисления параметров (CLR) и отображающий их в цикле.

Если четвертый параметр добавляется в перечисление "Параметры ", этот код сообщает о новом параметре без повторной компиляции, даже если перечисление определено в отдельной сборке.

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

Пример: элементы и свойства GetType

Объект GetType поддерживает ряд элементов и свойств, которые можно использовать для проверки типа. Этот код извлекает и отображает некоторые из этих сведений:

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

Пример: перечисление типов

Отражение также позволяет перечислять типы в сборке и членах в классах. Чтобы продемонстрировать эту функцию, определите простой класс:

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

Пример: проверка сборок

Если приведенный выше код компилируется в библиотеку DLL с именем vcpp_reflection_6.dll, можно использовать отражение для проверки содержимого этой сборки. Это включает использование функции API статического отражения xref:System.Reflection.Assembly.Load%2A?displayProperty=nameWithType для загрузки сборки. Эта функция возвращает адрес объекта сборки, который затем можно запрашивать о модулях и типах внутри.

После успешной загрузки сборки массив объектов Type извлекается с Assembly.GetTypes помощью функции. Каждый элемент массива содержит сведения о другом типе, хотя в этом случае определяется только один класс. Используя цикл, каждый тип в этом массиве запрашивается о членах типа с помощью функции Type::GetMembers. Эта функция возвращает массив объектов MethodInfo , каждый объект, содержащий сведения о функции-члене, члене данных или свойстве типа.

Обратите внимание, что список методов включает функции, явно определенные в TestClass , и функции, неявно унаследованные от класса System::Object . В рамках описания в .NET, а не в синтаксисе Visual C++ свойства отображаются как базовый элемент данных, к которым обращается функция get/set. Функции get/set отображаются в этом списке как обычные методы. Отражение поддерживается с помощью среды CLR, а не компилятора Microsoft C++.

Хотя этот код использовался для проверки определенной сборки, вы также можете использовать этот код для проверки сборок .NET. Например, если изменить TestAssembly на mscorlib, вы увидите список всех типов и методов, определенных в 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);
}

Практическое руководство. Реализация архитектуры компонентов подключаемого модуля с помощью отражения

В следующих примерах кода демонстрируется использование отражения для реализации простой архитектуры подключаемых модулей. Первый список — это приложение, а второй — подключаемый модуль. Приложение представляет собой несколько форм документов, заполняющихся любым классом на основе форм, найденным в библиотеке DLL подключаемого модуля в качестве аргумента командной строки.

Приложение пытается загрузить предоставленную сборку с помощью System.Reflection.Assembly.Load метода. В случае успешного выполнения типы внутри сборки перечисляются с помощью System.Reflection.Assembly.GetTypes метода. Затем каждый тип проверяется на совместимость с помощью System.Type.IsAssignableFrom метода. В этом примере классы, найденные в предоставленной сборке, должны быть производными от Form класса, чтобы квалифицироваться как подключаемый модуль.

Затем совместимые классы создаются с System.Activator.CreateInstance помощью метода, который принимает Type в качестве аргумента и возвращает указатель на новый экземпляр. Затем каждый новый экземпляр присоединяется к форме и отображается.

Обратите внимание, что Load метод не принимает имена сборок, которые включают расширение файла. Основная функция в приложении обрезает все предоставленные расширения, поэтому в любом случае работает следующий пример кода.

Пример приложения

Следующий код определяет приложение, которое принимает подключаемые модули. Имя сборки должно быть указано в качестве первого аргумента. Эта сборка должна содержать по крайней мере один общедоступный Form производный тип.

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

Примеры подключаемых модулей

Следующий код определяет три класса, производных от Form. Когда имя результирующей сборки передается исполняемому файлу в предыдущем списке, каждый из этих трех классов будет обнаружен и создан экземпляр, несмотря на то, что все они были неизвестны для приложения размещения во время компиляции.

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

Практическое руководство. Перечисление типов данных в сборках с помощью отражения

В следующем коде показано перечисление общедоступных типов и членов с помощью System.Reflection.

Если имя сборки в локальном каталоге или в GAC, приведенный ниже код пытается открыть сборку и получить описания. При успешном выполнении каждый тип отображается со своими общедоступными элементами.

Обратите внимание, что требуется, чтобы System.Reflection.Assembly.Load расширение файла не использовалось. Таким образом, использование "mscorlib.dll" в качестве аргумента командной строки завершится ошибкой, при использовании просто mscorlib будет отображаться платформа .NET Framework типов. Если имя сборки не указано, код обнаружит и сообщит о типах в текущей сборке (EXE, полученный из этого кода).

Пример

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

См. также