反映 (C++/CLI)
反映 (Reflection) 允許在執行階段檢查已知的資料型別。 反映允許指定組件中資料型別列舉,並且會發現指定類別的成員或實值型別。 不管該型別是已知的或者在編譯時才參考的,它都會這樣做。 這樣會讓反映成為開發及程式碼管理工具的有用功能。
請注意,提供的組件名稱為強式名稱 (請參閱強式名稱的組件),其中包括組件版本、文化特性和簽章資訊。 同時也請注意,可以在擷取基底類別名稱時一起擷取命名空間的名稱 (資料型別是在此命名空間中定義的)。
存取反映功能最常用的方式就是透過 GetType 方法。 這個方法是由 System::Object 提供,所有的記憶體回收類別也衍生於此。
如果使用 /clr:pure 或 /clr:safe 編譯器選項建置 .exe,則在以 Visual C++ 編譯器建置的 .exe 上允許反映。 如需詳細資訊,請參閱/clr (Common Language Runtime 編譯)。
本節主題:
如需詳細資訊,請參閱 System.Reflection 命名空間。
範例
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());
}
實值型別也可以與 GetType 函式一起使用,但必須先執行 box 動作。
// 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());
}
如果使用 GetType 方法,typeid 運算子會將指標傳回至 [Type] 物件,因此這項程式碼會指出型別名稱 System.Int32。 顯示型別名稱是反映的最基本功能,但是反映還具備另一項可能更有用的技術,就是檢查或探索 (Discover) 列舉型別的有效值。 若要達成此目的,可以使用靜態 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);
}
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);
}
反映也允許組件內可以有列舉型別和類別內可以有成員。 若要示範此功能,請定義簡單類別:
// 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 函式 Assembly::Load 以載入組件, 這個函式會傳回 Assembly 物件的位址,之後便可在其內查詢物件的模組和型別。
一旦反映系統成功地載入組件,就會以 Assembly::GetTypes 函式擷取 [Type] 物件陣列。 每個陣列元素都會包含不同型別的相關資訊 (即使在這個範例中只定義一個類別)。 若使用迴圈,就可使用 Type::GetMembers 函式查詢此陣列中每個 Type 的型別成員。 這個函式會傳回 MethodInfo 物件陣列,其中每個物件都包含成員函式、資料成員或型別屬性 (Property) 的相關資訊。
請注意,方法清單包括 TestClass 中明確定義的函式,以及從 System::Object 類別中隱含繼承的函式。 由於是用 .NET 而非 Visual C++ 語法來描述的部分,屬性 (Property) 會顯示為基礎資料成員,須以 get/set 函式來存取。 get/set 函式會以規則性方法出現在此清單中。 反映的支援不是透過 Visual C++ 編譯器,而是透過 Common Language Runtime。
雖然可以使用此程式碼檢查所定義的組件,但是也可以使用此程式碼檢查 .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);
}