Отражение (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);
}