共用方式為


反射 (C++/CLI)

在執行時,反射允許檢查已知的數據類型。 反映允許列舉指定元件中的數據類型,而且可以探索指定類別或實值型別的成員。 不論類型在編譯時期是已知還是參考,都是如此。 這可讓反映成為開發和程式碼管理工具的實用功能。

請注意,提供的元件名稱是強名稱(請參閱 建立和使用強名稱元件),其中包含元件版本、文化特性和簽署資訊。 另請注意,可以擷取定義數據類型的命名空間名稱,以及基類的名稱。

存取反映功能最常見的方式是透過 GetType 方法。 此方法由 System.Object提供,所有垃圾收集類別都會從中衍生。

注意

只有使用 Microsoft C++ 編譯器並搭配 /clr:pure/clr:safe 編譯選項建置 .exe 時,才允許對該 .exe 進行反映。 /clr:pure/clr:safe 編譯程序選項在 Visual Studio 2015 中已被取代,在 Visual Studio 2017 中無法使用。 如需詳細資訊,請參閱 /clr(Common Language Runtime 編譯)。

如需詳細資訊,請參閱 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'

範例:Boxed 實值型別

數值型別也可搭配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 使用 Type::GetMembers 函式來查詢其類型成員。 此函式會傳回 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);
}

另請參閱