动态加载和使用类型

反射提供语言编译器用于实现隐式后期绑定的基础结构。 绑定是查找与唯一指定类型对应的声明(即实现)的过程。 在运行时而不是在编译时发生此过程时,称为后期绑定。 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.CreateInstanceAssembly.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.BindToMethodBinder.BindToField 方法用于解析与单个成员的绑定。 Binder.BindToMethod还可以通过getset属性访问器和属性访问器提供属性解析。

BindToMethod 如果不可能调用,则返回要 MethodBase 调用的引用或 null 引用(Nothing 在 Visual Basic 中)。 MethodBase返回值不需要是匹配参数中包含的值之一,尽管这是通常的情况。

当存在 ByRef 参数时,调用方可能想要返回它们。 因此,如果作了参数数组, Binder 客户端可以将参数数组映射回其原始形式 BindToMethod 。 为此,必须保证调用方参数的顺序不变。 当参数按名称传递时, Binder 对参数数组进行重新排序,这就是调用方看到的。 有关详细信息,请参阅 Binder.ReorderArgumentArray

可用成员集是在类型或任何基类型中定义的成员。 如果 BindingFlags 已指定,则会在集中返回任何辅助功能的成员。 如果未 BindingFlags.NonPublic 指定,绑定器必须强制实施辅助功能规则。 指定 PublicNonPublic 绑定标志时,还必须指定 InstanceStatic 绑定标志,否则不会返回任何成员。

如果给定名称中只有一个成员,则不需要回调,并且对该方法执行绑定。 代码示例的案例 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.GetConstructorType.GetMethod并通过 Type.GetProperty 为该成员提供签名信息来搜索当前类型的特定成员。 Binder.SelectMethodBinder.SelectProperty 重新调用以选择相应方法的给定签名信息。

另请参阅