動態載入和使用型別

更新:2007 年 11 月

反映 (Reflection) 提供語言編譯器 (例如 Microsoft Visual Basic 2005 和 JScript) 用來實作隱含晚期繫結的基礎結構。繫結是找出對應唯一指定型別的宣告 (即實作) 的過程。當這個過程出現在執行階段而非編譯時期時,就稱為晚期繫結。Visual Basic 2005 可以讓您在程式碼中使用隱含晚期繫結,Visual Basic 編譯器會呼叫使用反映取得物件型別的 Helper 方法。傳遞至 Helper 方法的引數使得適當的方法在執行階段被叫用。這些引數是用以叫用方法的執行個體 (物件)、叫用的方法的名稱 (字串),和傳遞至叫用的方法的引數 (物件陣列)。

在下列程式碼範例中,Visual Basic 編譯器隱含地使用反映呼叫物件上的方法,其型別在編譯時間為未知。HelloWorld 類別具有 PrintHello 方法,印出與傳遞至 PrintHello 方法的一些文字串連的 "Hello World"。這個範例中呼叫的 PrintHello 方法實際上是 Type.InvokeMember;Visual Basic 程式碼允許叫用 PrintHello 方法,就好像物件 (helloObj) 的型別在編譯時間 (早期繫結) 已經知道,而非在執行階段 (晚期繫結)。

Imports System
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 類別提供成員選取和引動過程的自訂控制項。

使用自訂繫結,您可以在執行階段載入組件,取得該組件中型別的資訊,指定您想要的型別,並接著叫用方法,或存取該型別上的欄位或屬性。這個技術很有用,如果您在編譯時間不知道物件的型別,例如當物件型別靠使用者輸入時。

以下範例示範不提供引數型別轉換的簡單自訂繫結器 (Binder)。Simple_Type.dll 的程式碼位於主要範例之前。在建置時間中,請務必建置 Simple_Type.dll,然後再將參考納入專案中。

' Code for building Simple_Type.dll.
Imports System

Namespace Simple_Type
    Public Class MySimpleClass
        Public Overloads Sub MyMethod(ByVal str As String, 
            ByVal i As Integer)
            Console.WriteLine("MyMethod parameters: {0}, {1}", str, i)
        End Sub 'MyMethod

        Public Overloads Sub MyMethod(ByVal str As String, 
            ByVal i As Integer, ByVal j As Integer)
            Console.WriteLine("MyMethod parameters: {0}, {1}, {2}", str, 
                i, j)
        End Sub 'MyMethod
    End Class 'MySimpleClass
End Namespace 'Simple_Type

Imports System
Imports System.Reflection
Imports System.Globalization
Imports Simple_Type.Simple_Type

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 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 'Main
    End Class 'MyMainClass

    '****************************************************
    ' A simple custom binder that provides no
    ' argument type conversion.
    '****************************************************
    Class MyCustomBinder
        Inherits Binder

        Public Overrides Function BindToMethod(ByVal bindingAttr As 
            BindingFlags, ByVal match() As MethodBase, ByRef args() As 
                Object, ByVal modifiers() As ParameterModifier, ByVal 
                    culture As CultureInfo, ByVal 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.
            Dim mb As MethodBase
            For Each mb In match
                Dim parameters As ParameterInfo() = mb.GetParameters()
                If ParametersMatch(parameters, args) Then
                    Return mb
                End If
            Next mb
            Return Nothing
        End Function 'BindToMethod

        Public Overrides Function BindToField(ByVal bindingAttr As 
            BindingFlags, ByVal match() As FieldInfo, ByVal value As 
                Object, ByVal culture As CultureInfo) As FieldInfo
            If match Is Nothing Then
                Throw New ArgumentNullException("match")
            End If
            Dim fi As FieldInfo
            For Each fi In match
                If fi.GetType() Is value.GetType() Then
                    Return fi
                End If
            Next fi
            Return Nothing
        End Function 'BindToField

        Public Overrides Function SelectMethod(ByVal bindingAttr As 
            BindingFlags, ByVal match() As MethodBase, ByVal types() As 
                Type, ByVal 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.
            Dim mb As MethodBase
            For Each mb In match
                Dim parameters As ParameterInfo() = mb.GetParameters()
                If ParametersMatch(parameters, types) Then
                    Return mb
                End If
            Next mb
            Return Nothing
        End Function 'SelectMethod

        Public Overrides Function SelectProperty(ByVal bindingAttr As 
            BindingFlags, ByVal match() As PropertyInfo, ByVal returnType 
                As Type, ByVal indexes() As Type, ByVal modifiers() As 
                    ParameterModifier) As PropertyInfo
            If match Is Nothing Then
                Throw New ArgumentNullException("match")
            End If
            Dim pi As PropertyInfo
            For Each pi In match
                If pi.GetType() Is returnType And 
                    ParametersMatch(pi.GetIndexParameters(), indexes) Then
                    Return pi
                End If
            Next pi
            Return Nothing
        End Function 'SelectProperty

        Public Overrides Function ChangeType(ByVal value As Object, 
            ByVal myChangeType As Type, ByVal 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
            End Try
        End Function 'ChangeType

        Public Overrides Sub ReorderArgumentArray(ByRef args() As Object, 
            ByVal 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 'ReorderArgumentArray

        ' Returns true only if the type of each object in a matches
        ' the type of each corresponding object in b.
        Private Overloads Function ParametersMatch(ByVal a() As 
            ParameterInfo, ByVal b() As Object) As Boolean
            If a.Length <> b.Length Then
                Return False
            End If
            Dim i As Integer
            For i = 0 To a.Length - 1
                If Not (a(i).ParameterType Is b(i).GetType()) Then
                    Return False
                End If
            Next i
            Return True
        End Function 'ParametersMatch

        ' Returns true only if the type of each object in a matches
        ' the type of each corresponding entry in b.
        Private Overloads Function ParametersMatch(ByVal a() As 
            ParameterInfo, ByVal b() As Type) As Boolean
            If a.Length <> b.Length Then
                Return False
            End If
            Dim i As Integer
            For i = 0 To a.Length - 1
                If Not (a(i).ParameterType Is b(i)) Then
                    Return False
                End If
            Next i
            Return True
        End Function 'ParametersMatch
    End Class 'MyCustomBinder
End Namespace 'Custom_Binder

// Code for building SimpleType.dll.
using System;

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);
        }
    }
}


using System;
using System.Reflection;
using System.Globalization;
using Simple_Type;
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;
        }
    }
}

InvokeMember 和 CreateInstance

使用 Type.InvokeMember 叫用型別成員。各種類別的 CreateInstance 方法,例如 System.ActivatorSystem.Reflection.Assembly,都是 InvokeMember 的特殊形式,可以建立指定型別的新執行個體。Binder 類別被使用於這些方法中的多載解析 (Overload Resolution) 和引數強制。

下列程式碼範例說明引數強制型轉 (型別轉換) 和成員選取的三種可能組合。案例 1,不需要引數強制或成員選取。案例 2,只需要成員選取。案例 3,只需要引數強制。

public class CustomBinderDriver
{
    public static void Main (string[] arguments)
    {
    Type t = typeof (CustomBinderDriver);
    CustomBinder binder = new CustomBinder();
    BindingFlags flags = BindingFlags.InvokeMethod|BindingFlags.Instance|
        BindingFlags.Public|BindingFlags.Static;

    // 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);
    }
}

當有多個具相同名稱的成員可供使用時,需要多載解析。Binder.BindToMethodBinder.BindToField 方法用來解析單一成員的繫結。Binder.BindToMethod 也透過 getset 屬性存取子提供屬性解析。

BindToMethod 會傳回要叫用的 MethodBase,如果無法叫用,則會傳回 null 參考 (Visual Basic 中為 Nothing)。MethodBase 傳回值不一定要是那些包含於 match 參數的其中之一,雖然常是如此。

當 ByRef 引數出現時,呼叫端可能會想將它們取回。因此,Binder 允許用戶端將引數陣列對應回到它的原來形式,如果 BindToMethod 已經操作引數陣列的話。為了這樣做,呼叫端必須保證不變更引數的順序。當引數按名稱傳遞時,繫結器 Binder 會重新排列引述陣列,而那就是呼叫端所看到的。如需詳細資訊,請參閱 Binder.ReorderArgumentArray

可用成員的集合為那些在型別或任何基底型別 (Base Type) 中定義的成員。如果 BindingFlags.NonPublic 已指定,任何存取範圍的成員將在集合中傳回。如果不指定 BindingFlags.NonPublic,繫結器必須強制使用存取範圍規則。指定 PublicNonPublic 繫結旗標時,您也必須指定 InstanceStatic 繫結旗標,否則將不會傳回成員。

如果只有一個指定名稱的成員,則沒有回呼 (Callback) 的必要,而繫結即在那個方法上完成。程式碼範例的案例 1 說明了這一點:只有一個 PrintBob 方法可用,因此不需要回呼。

如果有多個成員在可用集合中,這些方法會全部傳遞至 BindToMethod,以選取適當的方法並傳回它。程式碼範例的案例 2 中,有兩個名為 PrintValue 的方法。適當的方法經由呼叫 BindToMethod 來選取。

ChangeType 執行引數強制 (型別轉換),轉換實際引數至選取方法的型式引數 (Formal Argument) 型別。即使型別完全相符,ChangeType 仍會被每個引數呼叫。

在程式碼範例的案例 3 中,String 型別的實際引數,會將其值 "5.5" 傳遞至具有 Double 型別的型式引數 (Formal Argument) 的方法。為使引動過程成功,字串值 "5.5" 必須轉換為雙精度浮點數 (Double) 值。ChangeType 會負責執行轉換。

ChangeType 只會執行無遺漏或擴展強制型轉 (Coercion),如下表所示。

來源型別

目標型別

任何型別

其基底型別

任何型別

它實作的介面

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.GetConstructorType.GetMethodType.GetProperty 會藉由提供該成員的簽章資訊,搜尋目前型別的特殊成員。Binder.SelectMethodBinder.SelectProperty 會再次呼叫,以選取適當方法的指定簽章資訊。

請參閱

概念

檢視型別資訊

轉換概觀

參考

Type.InvokeMember

Assembly.Load