動態載入和使用類型
反映會提供語言編譯器所使用的基礎結構,以實作隱含晚期繫結。 繫結是尋找對應至唯一指定的類型宣告 (也就是實作) 的程序。 當此進程在運行時間發生,而不是在編譯時期發生時,它稱為晚期系結。 Visual Basic 可讓您在程式碼內使用隱含晚期繫結;Visual Basic 編譯器會呼叫 Helper 方法,它會使用反映來取得物件類型。 傳遞至 helper 方法的引數會導致在執行階段叫用適當的方法。 這些引數是在其上叫用方法的執行個體 (物件)、被叫用方法的名稱 (字串),以及傳遞給被叫用方法的引數 (物件陣列)。
在下列範例中,Visual Basic 編譯器會隱含地使用反映,對一個在編譯階段不知道其類型的物件呼叫方法。 類別 HelloWorld
有一個 PrintHello
方法,會列印出 「Hello World」 與傳遞至 PrintHello
方法的一些文字串連。 PrintHello
在此範例中呼叫的方法實際上是 ;Type.InvokeMemberVisual Basic 程式代碼允許PrintHello
叫用 方法,就像在編譯時期(helloObj
早期系結)而不是運行時間(晚期系結)已知物件類型一樣。
Module Hello
Sub Main()
' Sets up the variable.
Dim helloObj As Object
' Creates the object.
helloObj = new HelloWorld()
' Invokes the print method as if it was early bound
' even though it is really late bound.
helloObj.PrintHello("Visual Basic Late Bound")
End Sub
End Module
自定義系結
除了編譯器隱含地用來進行晚期繫結,反映也可明確地用於程式碼,來完成晚期繫結。
Common Language Runtime 支援多種程式設計語言,而這些語言的繫結規則不同。 在早期繫結案例中,程式碼產生器可以完全控制這個繫結。 不過,在透過反映的晚期繫結中,繫結必須受自訂繫結控制。 Binder 類別會提供成員選取和叫用的自訂控制。
您可以使用自訂繫結,在執行階段載入組件、取得該組件中的類型相關資訊、指定您要的類型,然後對該類型叫用方法或存取欄位或屬性。 如果您在編譯時期不知道對象的類型,例如當物件類型相依於使用者輸入時,這項技術會很有用。
下列範例將示範簡單的自訂繫結,它不提供任何引數類型轉換。 Simple_Type.dll
的程式碼在主要範例之前。 請務必建置 Simple_Type.dll
,然後在建置階段的專案中包含對它的參考。
// Code for building SimpleType.dll.
using namespace System;
using namespace System::Reflection;
using namespace System::Globalization;
namespace Simple_Type
{
public ref class MySimpleClass
{
public:
void MyMethod(String^ str, int i)
{
Console::WriteLine("MyMethod parameters: {0}, {1}", str, i);
}
void MyMethod(String^ str, int i, int j)
{
Console::WriteLine("MyMethod parameters: {0}, {1}, {2}",
str, i, j);
}
};
}
using namespace Simple_Type;
namespace Custom_Binder
{
// ****************************************************
// A simple custom binder that provides no
// argument type conversion.
// ****************************************************
public ref class MyCustomBinder : Binder
{
public:
virtual MethodBase^ BindToMethod(
BindingFlags bindingAttr,
array<MethodBase^>^ match,
array<Object^>^% args,
array<ParameterModifier>^ modifiers,
CultureInfo^ culture,
array<String^>^ names,
Object^% state) override
{
if (match == nullptr)
{
throw gcnew ArgumentNullException("match");
}
// Arguments are not being reordered.
state = nullptr;
// Find a parameter match and return the first method with
// parameters that match the request.
for each (MethodBase^ mb in match)
{
array<ParameterInfo^>^ parameters = mb->GetParameters();
if (ParametersMatch(parameters, args))
{
return mb;
}
}
return nullptr;
}
virtual FieldInfo^ BindToField(BindingFlags bindingAttr,
array<FieldInfo^>^ match, Object^ value, CultureInfo^ culture) override
{
if (match == nullptr)
{
throw gcnew ArgumentNullException("match");
}
for each (FieldInfo^ fi in match)
{
if (fi->GetType() == value->GetType())
{
return fi;
}
}
return nullptr;
}
virtual MethodBase^ SelectMethod(
BindingFlags bindingAttr,
array<MethodBase^>^ match,
array<Type^>^ types,
array<ParameterModifier>^ modifiers) override
{
if (match == nullptr)
{
throw gcnew ArgumentNullException("match");
}
// Find a parameter match and return the first method with
// parameters that match the request.
for each (MethodBase^ mb in match)
{
array<ParameterInfo^>^ parameters = mb->GetParameters();
if (ParametersMatch(parameters, types))
{
return mb;
}
}
return nullptr;
}
virtual PropertyInfo^ SelectProperty(
BindingFlags bindingAttr,
array<PropertyInfo^>^ match,
Type^ returnType,
array<Type^>^ indexes,
array<ParameterModifier>^ modifiers) override
{
if (match == nullptr)
{
throw gcnew ArgumentNullException("match");
}
for each (PropertyInfo^ pi in match)
{
if (pi->GetType() == returnType &&
ParametersMatch(pi->GetIndexParameters(), indexes))
{
return pi;
}
}
return nullptr;
}
virtual Object^ ChangeType(
Object^ value,
Type^ myChangeType,
CultureInfo^ culture) override
{
try
{
Object^ newType;
newType = Convert::ChangeType(value, myChangeType);
return newType;
}
// Throw an InvalidCastException if the conversion cannot
// be done by the Convert.ChangeType method.
catch (InvalidCastException^)
{
return nullptr;
}
}
virtual void ReorderArgumentArray(array<Object^>^% args,
Object^ state) override
{
// No operation is needed here because BindToMethod does not
// reorder the args array. The most common implementation
// of this method is shown below.
// ((BinderState^)state).args.CopyTo(args, 0);
}
// Returns true only if the type of each object in a matches
// the type of each corresponding object in b.
private:
bool ParametersMatch(array<ParameterInfo^>^ a, array<Object^>^ b)
{
if (a->Length != b->Length)
{
return false;
}
for (int i = 0; i < a->Length; i++)
{
if (a[i]->ParameterType != b[i]->GetType())
{
return false;
}
}
return true;
}
// Returns true only if the type of each object in a matches
// the type of each corresponding entry in b.
bool ParametersMatch(array<ParameterInfo^>^ a, array<Type^>^ b)
{
if (a->Length != b->Length)
{
return false;
}
for (int i = 0; i < a->Length; i++)
{
if (a[i]->ParameterType != b[i])
{
return false;
}
}
return true;
}
};
public ref class MyMainClass
{
public:
static void Main()
{
// Get the type of MySimpleClass.
Type^ myType = MySimpleClass::typeid;
// Get an instance of MySimpleClass.
MySimpleClass^ myInstance = gcnew MySimpleClass();
MyCustomBinder^ myCustomBinder = gcnew MyCustomBinder();
// Get the method information for the particular overload
// being sought.
MethodInfo^ myMethod = myType->GetMethod("MyMethod",
BindingFlags::Public | BindingFlags::Instance,
myCustomBinder, gcnew array<Type^> {String::typeid,
int::typeid}, nullptr);
Console::WriteLine(myMethod->ToString());
// Invoke the overload.
myType->InvokeMember("MyMethod", BindingFlags::InvokeMethod,
myCustomBinder, myInstance,
gcnew array<Object^> {"Testing...", (int)32});
}
};
}
int main()
{
Custom_Binder::MyMainClass::Main();
}
// Code for building SimpleType.dll.
using System;
using System.Reflection;
using System.Globalization;
using Simple_Type;
namespace Simple_Type
{
public class MySimpleClass
{
public void MyMethod(string str, int i)
{
Console.WriteLine("MyMethod parameters: {0}, {1}", str, i);
}
public void MyMethod(string str, int i, int j)
{
Console.WriteLine("MyMethod parameters: {0}, {1}, {2}",
str, i, j);
}
}
}
namespace Custom_Binder
{
class MyMainClass
{
static void Main()
{
// Get the type of MySimpleClass.
Type myType = typeof(MySimpleClass);
// Get an instance of MySimpleClass.
MySimpleClass myInstance = new MySimpleClass();
MyCustomBinder myCustomBinder = new MyCustomBinder();
// Get the method information for the particular overload
// being sought.
MethodInfo myMethod = myType.GetMethod("MyMethod",
BindingFlags.Public | BindingFlags.Instance,
myCustomBinder, new Type[] {typeof(string),
typeof(int)}, null);
Console.WriteLine(myMethod.ToString());
// Invoke the overload.
myType.InvokeMember("MyMethod", BindingFlags.InvokeMethod,
myCustomBinder, myInstance,
new Object[] {"Testing...", (int)32});
}
}
// ****************************************************
// A simple custom binder that provides no
// argument type conversion.
// ****************************************************
class MyCustomBinder : Binder
{
public override MethodBase BindToMethod(
BindingFlags bindingAttr,
MethodBase[] match,
ref object[] args,
ParameterModifier[] modifiers,
CultureInfo culture,
string[] names,
out object state)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
// Arguments are not being reordered.
state = null;
// Find a parameter match and return the first method with
// parameters that match the request.
foreach (MethodBase mb in match)
{
ParameterInfo[] parameters = mb.GetParameters();
if (ParametersMatch(parameters, args))
{
return mb;
}
}
return null;
}
public override FieldInfo BindToField(BindingFlags bindingAttr,
FieldInfo[] match, object value, CultureInfo culture)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
foreach (FieldInfo fi in match)
{
if (fi.GetType() == value.GetType())
{
return fi;
}
}
return null;
}
public override MethodBase SelectMethod(
BindingFlags bindingAttr,
MethodBase[] match,
Type[] types,
ParameterModifier[] modifiers)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
// Find a parameter match and return the first method with
// parameters that match the request.
foreach (MethodBase mb in match)
{
ParameterInfo[] parameters = mb.GetParameters();
if (ParametersMatch(parameters, types))
{
return mb;
}
}
return null;
}
public override PropertyInfo SelectProperty(
BindingFlags bindingAttr,
PropertyInfo[] match,
Type returnType,
Type[] indexes,
ParameterModifier[] modifiers)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
foreach (PropertyInfo pi in match)
{
if (pi.GetType() == returnType &&
ParametersMatch(pi.GetIndexParameters(), indexes))
{
return pi;
}
}
return null;
}
public override object ChangeType(
object value,
Type myChangeType,
CultureInfo culture)
{
try
{
object newType;
newType = Convert.ChangeType(value, myChangeType);
return newType;
}
// Throw an InvalidCastException if the conversion cannot
// be done by the Convert.ChangeType method.
catch (InvalidCastException)
{
return null;
}
}
public override void ReorderArgumentArray(ref object[] args,
object state)
{
// No operation is needed here because BindToMethod does not
// reorder the args array. The most common implementation
// of this method is shown below.
// ((BinderState)state).args.CopyTo(args, 0);
}
// Returns true only if the type of each object in a matches
// the type of each corresponding object in b.
private bool ParametersMatch(ParameterInfo[] a, object[] b)
{
if (a.Length != b.Length)
{
return false;
}
for (int i = 0; i < a.Length; i++)
{
if (a[i].ParameterType != b[i].GetType())
{
return false;
}
}
return true;
}
// Returns true only if the type of each object in a matches
// the type of each corresponding entry in b.
private bool ParametersMatch(ParameterInfo[] a, Type[] b)
{
if (a.Length != b.Length)
{
return false;
}
for (int i = 0; i < a.Length; i++)
{
if (a[i].ParameterType != b[i])
{
return false;
}
}
return true;
}
}
}
' Code for building SimpleType.dll.
Imports System.Reflection
Imports System.Globalization
Imports Simple_Type
Namespace Simple_Type
Public Class MySimpleClass
Public Sub MyMethod(str As String, i As Integer)
Console.WriteLine("MyMethod parameters: {0}, {1}", str, i)
End Sub
Public Sub MyMethod(str As String, i As Integer, j As Integer)
Console.WriteLine("MyMethod parameters: {0}, {1}, {2}",
str, i, j)
End Sub
End Class
End Namespace
Namespace Custom_Binder
Class MyMainClass
Shared Sub Main()
' Get the type of MySimpleClass.
Dim myType As Type = GetType(MySimpleClass)
' Get an instance of MySimpleClass.
Dim myInstance As New MySimpleClass()
Dim myCustomBinder As New MyCustomBinder()
' Get the method information for the particular overload
' being sought.
Dim myMethod As MethodInfo = myType.GetMethod("MyMethod",
BindingFlags.Public Or BindingFlags.Instance,
myCustomBinder, New Type() {GetType(String),
GetType(Integer)}, Nothing)
Console.WriteLine(myMethod.ToString())
' Invoke the overload.
myType.InvokeMember("MyMethod", BindingFlags.InvokeMethod,
myCustomBinder, myInstance,
New Object() {"Testing...", CInt(32)})
End Sub
End Class
' ****************************************************
' A simple custom binder that provides no
' argument type conversion.
' ****************************************************
Class MyCustomBinder
Inherits Binder
Public Overrides Function BindToMethod(bindingAttr As BindingFlags,
match() As MethodBase, ByRef args As Object(),
modIfiers() As ParameterModIfier, culture As CultureInfo,
names() As String, ByRef state As Object) As MethodBase
If match is Nothing Then
Throw New ArgumentNullException("match")
End If
' Arguments are not being reordered.
state = Nothing
' Find a parameter match and return the first method with
' parameters that match the request.
For Each mb As MethodBase in match
Dim parameters() As ParameterInfo = mb.GetParameters()
If ParametersMatch(parameters, args) Then
Return mb
End If
Next mb
Return Nothing
End Function
Public Overrides Function BindToField(bindingAttr As BindingFlags,
match() As FieldInfo, value As Object, culture As CultureInfo) As FieldInfo
If match Is Nothing
Throw New ArgumentNullException("match")
End If
For Each fi As FieldInfo in match
If fi.GetType() = value.GetType() Then
Return fi
End If
Next fi
Return Nothing
End Function
Public Overrides Function SelectMethod(bindingAttr As BindingFlags,
match() As MethodBase, types() As Type,
modifiers() As ParameterModifier) As MethodBase
If match Is Nothing Then
Throw New ArgumentNullException("match")
End If
' Find a parameter match and return the first method with
' parameters that match the request.
For Each mb As MethodBase In match
Dim parameters() As ParameterInfo = mb.GetParameters()
If ParametersMatch(parameters, types) Then
Return mb
End If
Next mb
Return Nothing
End Function
Public Overrides Function SelectProperty(
bindingAttr As BindingFlags, match() As PropertyInfo,
returnType As Type, indexes() As Type,
modIfiers() As ParameterModIfier) As PropertyInfo
If match Is Nothing Then
Throw New ArgumentNullException("match")
End If
For Each pi As PropertyInfo In match
If pi.GetType() = returnType And
ParametersMatch(pi.GetIndexParameters(), indexes) Then
Return pi
End If
Next pi
Return Nothing
End Function
Public Overrides Function ChangeType(
value As Object,
myChangeType As Type,
culture As CultureInfo) As Object
Try
Dim newType As Object
newType = Convert.ChangeType(value, myChangeType)
Return newType
' Throw an InvalidCastException If the conversion cannot
' be done by the Convert.ChangeType method.
Catch
Return Nothing
End Try
End Function
Public Overrides Sub ReorderArgumentArray(ByRef args() As Object, state As Object)
' No operation is needed here because BindToMethod does not
' reorder the args array. The most common implementation
' of this method is shown below.
' ((BinderState)state).args.CopyTo(args, 0)
End Sub
' Returns true only If the type of each object in a matches
' the type of each corresponding object in b.
Private Overloads Function ParametersMatch(a() As ParameterInfo, b() As Object) As Boolean
If a.Length <> b.Length Then
Return false
End If
For i As Integer = 0 To a.Length - 1
If a(i).ParameterType <> b(i).GetType() Then
Return false
End If
Next i
Return true
End Function
' Returns true only If the type of each object in a matches
' the type of each corresponding enTry in b.
Private Overloads Function ParametersMatch(a() As ParameterInfo,
b() As Type) As Boolean
If a.Length <> b.Length Then
Return false
End If
For i As Integer = 0 To a.Length - 1
If a(i).ParameterType <> b(i)
Return false
End If
Next
Return true
End Function
End Class
End Namespace
InvokeMember 和 CreateInstance
使用 Type.InvokeMember 來叫用類型的成員。 CreateInstance
各種類別的方法,例如 Activator.CreateInstance 和 Assembly.CreateInstance,都是建立指定型別之新實例的特殊形式InvokeMember
。 類別 Binder
用於這些方法中的多載解析和自變數強制。
下列範例顯示引數強制型轉 (類型轉換) 和成員選取的三種可能組合。 案例 1 中不需要任何引數強制型轉或成員選取。 案例 2 中只需要成員選取。 案例 3 中只需要引數強制型轉。
public ref class CustomBinderDriver
{
public:
static void Main()
{
Type^ t = CustomBinderDriver::typeid;
CustomBinder^ binder = gcnew CustomBinder();
BindingFlags flags = BindingFlags::InvokeMethod | BindingFlags::Instance |
BindingFlags::Public | BindingFlags::Static;
array<Object^>^ args;
// Case 1. Neither argument coercion nor member selection is needed.
args = gcnew array<Object^> {};
t->InvokeMember("PrintBob", flags, binder, nullptr, args);
// Case 2. Only member selection is needed.
args = gcnew array<Object^> {42};
t->InvokeMember("PrintValue", flags, binder, nullptr, args);
// Case 3. Only argument coercion is needed.
args = gcnew array<Object^> {"5.5"};
t->InvokeMember("PrintNumber", flags, binder, nullptr, args);
}
static void PrintBob()
{
Console::WriteLine("PrintBob");
}
static void PrintValue(long value)
{
Console::WriteLine("PrintValue({0})", value);
}
static void PrintValue(String^ value)
{
Console::WriteLine("PrintValue\"{0}\")", value);
}
static void PrintNumber(double value)
{
Console::WriteLine("PrintNumber ({0})", value);
}
};
int main()
{
CustomBinderDriver::Main();
}
public class CustomBinderDriver
{
public static void Main()
{
Type t = typeof(CustomBinderDriver);
CustomBinder binder = new CustomBinder();
BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance |
BindingFlags.Public | BindingFlags.Static;
object[] args;
// Case 1. Neither argument coercion nor member selection is needed.
args = new object[] {};
t.InvokeMember("PrintBob", flags, binder, null, args);
// Case 2. Only member selection is needed.
args = new object[] {42};
t.InvokeMember("PrintValue", flags, binder, null, args);
// Case 3. Only argument coercion is needed.
args = new object[] {"5.5"};
t.InvokeMember("PrintNumber", flags, binder, null, args);
}
public static void PrintBob()
{
Console.WriteLine("PrintBob");
}
public static void PrintValue(long value)
{
Console.WriteLine("PrintValue({0})", value);
}
public static void PrintValue(string value)
{
Console.WriteLine("PrintValue\"{0}\")", value);
}
public static void PrintNumber(double value)
{
Console.WriteLine("PrintNumber ({0})", value);
}
}
Public Class CustomBinderDriver
Public Shared Sub Main()
Dim t As Type = GetType(CustomBinderDriver)
Dim binder As New CustomBinder()
Dim flags As BindingFlags = BindingFlags.InvokeMethod Or BindingFlags.Instance Or
BindingFlags.Public Or BindingFlags.Static
Dim args() As Object
' Case 1. Neither argument coercion nor member selection is needed.
args = New object() {}
t.InvokeMember("PrintBob", flags, binder, Nothing, args)
' Case 2. Only member selection is needed.
args = New object() {42}
t.InvokeMember("PrintValue", flags, binder, Nothing, args)
' Case 3. Only argument coercion is needed.
args = New object() {"5.5"}
t.InvokeMember("PrintNumber", flags, binder, Nothing, args)
End Sub
Public Shared Sub PrintBob()
Console.WriteLine("PrintBob")
End Sub
Public Shared Sub PrintValue(value As Long)
Console.WriteLine("PrintValue ({0})", value)
End Sub
Public Shared Sub PrintValue(value As String)
Console.WriteLine("PrintValue ""{0}"")", value)
End Sub
Public Shared Sub PrintNumber(value As Double)
Console.WriteLine("PrintNumber ({0})", value)
End Sub
End Class
有多個相同名稱的成員可用時,需要多載解析。 Binder.BindToMethod 和 Binder.BindToField 方法用來解析對單一成員的繫結。 Binder.BindToMethod
也會透過 get
和 set
屬性存取子提供屬性解析。
BindToMethod
會傳 MethodBase 回 要叫用的 ;如果沒有這類叫用,則傳回 Null 參考 (Nothing
在 Visual Basic 中為 )。 雖然這是一般情況,但傳MethodBase
回值不需要包含在 match 參數中的其中一個。
有 ByRef 引數存在時,呼叫端可能會想要取回它們。 因此,如果已操作自變數陣列, Binder
用戶端就可以將自變數數位對應回其原始形式 BindToMethod
。 若要這樣做,呼叫端必須保證自變數的順序不變。 當自變數以名稱傳遞時, Binder
請重新排序自變數陣列,這就是呼叫端看到的內容。 如需詳細資訊,請參閱Binder.ReorderArgumentArray。
可用成員的集合是在類型或任何基底類型中定義的成員。 如果 BindingFlags 指定 ,則會在集合中傳回任何輔助功能的成員。 如果未 BindingFlags.NonPublic
指定,系結器必須強制執行輔助功能規則。 指定 或 系Public
結旗標時,您也必須指定 Instance
或 NonPublic
Static
系結旗標,否則不會傳回任何成員。
如果指定名稱的成員只有一個,便不需要回呼,會對該方法進行任何繫結。 程式代碼範例的案例 1 說明這一點:只有一個 PrintBob
方法可用,因此不需要回呼。
如果可用集合中有一個以上的成員,這些方法都會傳遞至 BindToMethod
,這會選取適當的方法並傳回它。 在程式代碼範例的案例 2 中,有兩個方法名為 PrintValue
。 呼叫 時會選取 BindToMethod
適當的方法。
ChangeType 會執行引數強制型轉 (類型轉換),這會將實際引數轉換為所選方法的正式引數的類型。 ChangeType
即使型別完全相符,也會針對每個自變數呼叫 。
在程式代碼範例的案例 3 中,類型為 「5.5」 的實際自變數 String
會傳遞至型 Double
別的型別方法。 叫用要成功,字串值 "5.5" 必須轉換為雙精度浮點數值。 ChangeType
會執行此轉換。
ChangeType
只會執行無損失或 擴大強制,如下表所示。
來源類型 | 目標類型 |
---|---|
任何型別 | 基底類型 |
任何型別 | 實作的介面 |
Char | UInt16、UInt32、Int32、UInt64、Int64、Single、Double |
Byte | Char、UInt16、Int16、UInt32、Int32、UInt64、Int64、Single、Double |
SByte | Int16、Int32、Int64、Single、Double |
UInt16 | UInt32、Int32、UInt64、Int64、Single、Double |
Int16 | Int32、Int64、Single、Double |
UInt32 | UInt64、Int64、Single、Double |
Int32 | Int64、Single、Double |
UInt64 | Single、Double |
Int64 | Single、Double |
Single | Double |
非參考類型 | 參考類型 |
類別 Type 具有 Get
使用 型 Binder
別參數來解析特定成員參考的方法。 Type.GetConstructor、Type.GetMethod 和 Type.GetProperty 會搜尋目前類型的特定成員,方法是提供該成員的簽章資訊。 接著回來呼叫 Binder.SelectMethod 和 Binder.SelectProperty,選取適當方法的指定簽章資訊。