分享方式:


反映 (C++/CLI)

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

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

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

注意

只有在使用 /clr:pure /clr:safe 編譯器選項建置 .exe 時,才允許在以 Microsoft C++ 編譯器建 置的 .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 式搭配使用,但必須先進行 Boxed。

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

範例:型別的列舉

反思ion 也允許列舉元件內的型別,以及類別內的成員。 若要示範這項功能,請定義簡單的類別:

// 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。反思。Assembly.Load%2A?displayProperty=nameWithType 以載入元件。 此函式會傳回 Assembly 物件的位址 ,然後可查詢有關 內的模組和型別。

一旦反映系統成功載入元件,就會使用 Assembly.GetTypes 函式擷取 Type 物件的陣列 。 每個陣列元素都包含不同類型的相關資訊,但在此情況下,只會定義一個類別。 使用迴圈時, 會使用 Type::GetMembers 函式來查詢此陣列中的每個 Type 關於類型成員。 此函式會傳回 MethodInfo 物件的陣列 ,每個物件都包含類型中成員函式、資料成員或屬性的相關資訊。

請注意,方法清單包含 TestClass 明確定義的函式,以及隱含繼承自 System::Object 類別的 函式。 在 .NET 而非 Visual C++ 語法中描述時,屬性會顯示為 get/set 函式所存取的基礎資料成員。 get/set 函式會以一般方法的形式出現在此清單中。 反思透過 Common Language Runtime 支援,而不是由 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);
}

如何:使用 反思ion 實作外掛程式元件架構

下列程式碼範例示範如何使用反映來實作簡單的「外掛程式」架構。 第一個清單是應用程式,第二個是外掛程式。 應用程式是多個檔表單,會使用以命令列引數形式提供之外掛程式 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);
}

另請參閱