反射提供语言编译器用于实现隐式后期绑定的基础结构。 绑定是查找与唯一指定类型对应的声明(即实现)的过程。 在运行时而不是在编译时发生此过程时,称为后期绑定。 Visual Basic 允许在代码中使用隐式后期绑定;Visual Basic 编译器调用一个帮助程序方法,该方法使用反射来获取对象类型。 传递给帮助程序方法的参数会导致在运行时调用相应的方法。 这些参数是调用方法的实例(对象),调用方法的名称(字符串),以及传递给已调用方法的参数(对象数组)。
在以下示例中,Visual Basic 编译器隐式使用反射对在编译时未知的对象调用方法。 类 HelloWorld
具有一个 PrintHello
方法,该方法打印出与传递给 PrintHello
该方法的某些文本串联的“Hello World”。
PrintHello
此示例中调用的方法实际上是一个Type.InvokeMember;Visual 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
自定义绑定
除了由编译器隐式用于后期绑定之外,反射还可以在代码中显式用于完成后期绑定。
公共语言运行时支持多种编程语言,这些语言的绑定规则有所不同。 在早期绑定的情况下,代码生成器可以完全控制此绑定。 但是,在后期通过反射绑定时,绑定必须由自定义绑定控制。 该 Binder 类提供成员选择和调用的自定义控件。
使用自定义绑定,可以在运行时加载程序集、获取有关该程序集中的类型的信息、指定所需的类型,然后调用方法或访问该类型的字段或属性。 如果在编译时不知道对象的类型,例如对象类型依赖于用户输入,则此方法非常有用。
下面的示例演示了一个简单的自定义绑定器,它不提供参数类型转换。
Simple_Type.dll
主要示例前面的代码。 请务必生成 Simple_Type.dll
,然后在生成时在项目中包含对它的引用。
// 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 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({value})");
}
public static void PrintValue(string value)
{
Console.WriteLine("PrintValue\"{0}\")", value);
}
public static void PrintNumber(double value)
{
Console.WriteLine($"PrintNumber ({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
返回值不需要是匹配参数中包含的值之一,尽管这是通常的情况。
当存在 ByRef 参数时,调用方可能想要返回它们。 因此,如果作了参数数组, Binder
客户端可以将参数数组映射回其原始形式 BindToMethod
。 为此,必须保证调用方参数的顺序不变。 当参数按名称传递时, Binder
对参数数组进行重新排序,这就是调用方看到的。 有关详细信息,请参阅 Binder.ReorderArgumentArray。
可用成员集是在类型或任何基类型中定义的成员。 如果 BindingFlags 已指定,则会在集中返回任何辅助功能的成员。 如果未 BindingFlags.NonPublic
指定,绑定器必须强制实施辅助功能规则。 指定 Public
或 NonPublic
绑定标志时,还必须指定 Instance
或 Static
绑定标志,否则不会返回任何成员。
如果给定名称中只有一个成员,则不需要回调,并且对该方法执行绑定。 代码示例的案例 1 说明了这一点:只有一种方法 PrintBob
可用,因此不需要回调。
如果可用集中有多个成员,则会将所有这些方法传递给 BindToMethod
该成员,该方法选择相应的方法并返回它。 在代码示例的案例 2 中,有两个命名 PrintValue
的方法。 调用时 BindToMethod
会选择适当的方法。
ChangeType 执行参数强制(类型转换),它将实际参数转换为所选方法的正式参数的类型。
ChangeType
即使类型完全匹配,也会为每个参数调用 。
在代码示例的案例 3 中,一个值为“5.5”的类型 String
的实际参数传递给具有类型 Double
正式参数的方法。 若要成功调用,字符串值“5.5”必须转换为双精度值。
ChangeType
执行此转换。
ChangeType
仅执行无损失或 扩大强制,如下表所示。
源类型 | 目标类型 |
---|---|
任意类型 | 其基类型 |
任意类型 | 它实现的接口 |
煳 | 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 |
国际 32 | Int64、Single、Double |
UInt64 | Single、Double |
Int64 系列 | Single、Double |
单身 | 加倍 |
非引用类型 | 引用类型 |
该 Type 类具有 Get
使用类型的 Binder
参数解析对特定成员的引用的方法。
Type.GetConstructor, Type.GetMethod并通过 Type.GetProperty 为该成员提供签名信息来搜索当前类型的特定成员。
Binder.SelectMethod 并 Binder.SelectProperty 重新调用以选择相应方法的给定签名信息。