反射 (C++/CLI)

反射允许在运行时检查已知数据类型。 反射允许枚举给定程序集中的数据类型,并且可以发现给定类或值类型的成员。 无论类型在编译时是已知还是引用,情况都是如此。 这使反射成为开发和代码管理工具的有用功能。

请注意,提供的程序集名称是强名称(请参阅创建和使用具有强名称的程序集),其中包含程序集版本、区域性和签名信息。 另请注意,可以检索在其中定义数据类型的命名空间的名称以及基类的名称。

访问反射功能的最常见方法是通过 GetType 方法。 此方法由 System.Object(从中派生所有垃圾回收类)提供。

注意

仅当使用 /clr:pure 或 /clr:safe 编译器选项生成 .exe时,才允许对使用 Microsoft C++ 编译器生成的 .exe 进行反射。 /clr:pure 和 /clr:safe 编译器选项在 Visual Studio 2015 中已弃用,在 Visual Studio 2017 中不可用。 有关详细信息,请参阅 /clr(公共语言运行时编译)

有关详细信息,请参阅 System.Reflection

示例:GetType

GetType 方法返回指向 Type 类对象(描述对象所基于的类型)的指针。 (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 函数来完成,该函数返回字符串数组,其中每个字符串都包含文本形式的枚举值。 以下示例检索一个描述 Options (CLR) 枚举的枚举值的字符串数组,并在循环中显示它们

如果将第四个选项添加到 Options 枚举中,则此代码会报告新选项而不重新编译,即使枚举在单独程序集中定义也是如此

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

示例:程序集的检查

如果以上代码编译为名为 vcpp_reflection_6.dll 的 DLL,则可以使用反射检查此程序集的内容。 这涉及到使用静态反射 API 函数 xref:System.Reflection.Assembly.Load%2A?displayProperty=nameWithType 加载程序集。 此函数返回 Assembly 对象的地址,随后可以对该对象查询其中的模块和类型

反射系统成功加载程序集后,使用 Assembly.GetTypes 函数检索 Type 对象的数组。 每个数组元素都包含有关不同类型的信息,但在此例中,只定义了一个类。 借助循环,使用 Type::GetMembers 函数对此数组中的每个 Type 查询类型成员。 此函数返回 MethodInfo 对象的数组,其中每个对象包含有关类型中成员函数、数据成员或属性的信息

请注意,方法列表包含在 TestClass 中显式定义的函数,以及从 System::Object 类隐式继承的函数。 作为采用 .NET 而不是 Visual C++ 语法进行描述的一部分,属性显示为 get/set 函数访问的基础数据成员。 get/set 函数作为常规方法出现在此列表中。 通过公共语言运行时(而不是 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);
}

另请参阅