Udostępnij za pomocą


Dynamiczne ładowanie i używanie typów

Odbicie zapewnia infrastrukturę używaną przez kompilatory języka do implementowania niejawnego powiązania opóźnionego. Powiązanie to proces lokalizowania deklaracji (czyli implementacji), która odpowiada unikatowo określonemu typowi. Gdy ten proces występuje w czasie wykonywania, a nie w czasie kompilacji, jest nazywany późnym powiązaniem. Visual Basic umożliwia używanie niejawnego opóźnionego powiązania w kodzie; kompilator języka Visual Basic wywołuje metodę pomocnika, która używa odbicia w celu uzyskania typu obiektu. Argumenty przekazane do metody pomocniczej powodują wywołanie odpowiedniej metody w czasie wykonywania. Te argumenty to wystąpienie (obiekt), na którym można wywołać metodę, nazwę wywoływanej metody (ciąg) oraz argumenty przekazane do metody wywoływanej (tablica obiektów).

W poniższym przykładzie kompilator języka Visual Basic używa odbicia niejawnie w celu wywołania metody na obiekcie, którego typ nie jest znany w czasie kompilacji. Klasa HelloWorld ma metodę PrintHello, która wyświetla ciąg "Hello World" połączony z tekstem przekazanym do metody PrintHello. Metoda wywoływana PrintHello w tym przykładzie jest rzeczywiście metodą Type.InvokeMember; kod języka Visual Basic umożliwia PrintHello wywoływanie metody tak, jakby typ obiektu (helloObj) był znany w czasie kompilacji (wczesne powiązanie), a nie w czasie wykonywania (opóźnione powiązanie).

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

Wiązanie niestandardowe

Oprócz tego, że jest używana niejawnie przez kompilatory do późnego powiązania, refleksja może być używana także jawnie w kodzie, aby realizować późne wiązanie.

Środowisko uruchomieniowe języka wspólnego obsługuje wiele języków programowania, a reguły powiązań tych języków różnią się. We wczesnym przypadku generatory kodu mogą całkowicie kontrolować to powiązanie. Jednak w późnym wiązaniu poprzez refleksję, wiązanie musi być kontrolowane przez spersonalizowane powiązanie. Klasa Binder zapewnia niestandardową kontrolę nad wyborem członków i ich wywoływaniem.

Za pomocą powiązania niestandardowego można załadować zestaw w czasie wykonywania, uzyskać informacje o typach w tym zestawie, określić żądany typ, a następnie wywołać metody lub pola dostępu lub właściwości tego typu. Ta technika jest przydatna, jeśli nie znasz typu obiektu w czasie kompilacji, na przykład gdy typ obiektu jest zależny od danych wejściowych użytkownika.

W poniższym przykładzie pokazano prosty niestandardowy binder, który nie zapewnia konwersji typu argumentu. Kod Simple_Type.dll poprzedza główny przykład. Pamiętaj, aby skompilować Simple_Type.dll , a następnie dołączyć odwołanie do niego w projekcie w czasie kompilacji.

// 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 i CreateInstance

Użyj Type.InvokeMember do wywołania elementu członkowskiego typu. CreateInstance Metody różnych klas, takich jak Activator.CreateInstance i Assembly.CreateInstance, są wyspecjalizowanymi formamiInvokeMember, które tworzą nowe wystąpienia określonego typu. Binder klasa jest używana do rozwiązywania przeciążeń i przymuszania argumentów w tych metodach.

W poniższym przykładzie przedstawiono trzy możliwe kombinacje wymuszania argumentu (konwersji typu) i wyboru członka. W przypadku 1 nie jest wymagane wymuszanie argumentów ani wybór składowych. W przypadku 2 wymagany jest tylko wybór członków. W przypadku 3 wymagany jest tylko przymus argumentu.

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

Rozwiązywanie przeciążenia jest konieczne, gdy dostępna jest więcej niż jedna funkcja o tej samej nazwie. Metody Binder.BindToMethod i Binder.BindToField służą do rozpoznawania powiązania z jednym elementem członkowskim. Binder.BindToMethod Zapewnia również rozpoznawanie właściwości za pośrednictwem akcesorów właściwości get i set.

BindToMethod zwraca MethodBase do wywołania lub odwołanie o wartości null (Nothing w Visual Basic), jeśli nie jest możliwe żadne takie wywołanie. Wartość MethodBase zwracana nie musi być jedną z tych zawartych w parametrze match , chociaż jest to zwykły przypadek.

Gdy argumenty ByRef są obecne, obiekt wywołujący może chcieć je odzyskać. Binder W związku z tym umożliwia klientowi zmapowanie tablicy argumentów z powrotem na jej oryginalną formę, jeśli BindToMethod manipulował tablicą argumentów. Aby to zrobić, obiekt wywołujący musi mieć gwarancję, że kolejność argumentów jest niezmieniona. Gdy argumenty są przekazywane według nazwy, Binder przekształca tablicę argumentów, co widzi wywołujący. Aby uzyskać więcej informacji, zobacz Binder.ReorderArgumentArray.

Zestaw dostępnych członków obejmuje te członki zdefiniowane w typie lub dowolnym typie podstawowym. Jeśli BindingFlags zostanie określony, zwracane są elementy o dowolnym poziomie dostępu. Jeśli BindingFlags.NonPublic nie zostanie określony, powiązanie musi wymuszać zasady dostępu. Podczas określania flagi Public lub NonPublic powiązania, należy również określić flagę Instance lub Static powiązania, albo nie zostaną zwrócone żadne elementy członkowskie.

Jeśli istnieje tylko jeden członek o podanej nazwie, nie jest wymagane wywołanie zwrotne, a powiązanie odbywa się za pomocą tej metody. Przypadek 1 przykładu kodu ilustruje ten punkt: Tylko jedna PrintBob metoda jest dostępna, dlatego nie jest wymagane żadne wywołanie zwrotne.

Jeśli w dostępnym zestawie znajduje się więcej niż jeden członek, wszystkie te metody są przekazywane do BindToMethod, który wybiera odpowiednią metodę i ją zwraca. W przypadku 2 przykładu kodu istnieją dwie metody o nazwie PrintValue. Odpowiednia metoda jest wybierana przez wywołanie BindToMethod.

ChangeType przeprowadza wymuszenie argumentów (konwersję typu), które przekształca faktyczne argumenty na typ argumentów formalnych wybranej metody. ChangeType jest wywoływany dla każdego z argumentów, nawet jeśli typy dokładnie się zgadzają.

W przypadku 3 przykładu kodu rzeczywisty argument typu String o wartości "5,5" jest przekazywany do metody z formalnym argumentem typu Double. Aby wywołanie powiodło się, należy przekonwertować wartość ciągu "5.5" na podwójną wartość. ChangeType wykonuje tę konwersję.

ChangeType Wykonuje tylko bezstratne lub rozszerzające przymusy, jak pokazano w poniższej tabeli.

Typ źródła Typ docelowy
Dowolny typ Jego typ podstawowy
Dowolny typ Interfejs, który implementuje
Char UInt16, UInt32, Int32, UInt64, Int64, Single, Double
Bajt 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 Pojedynczy, podwójny
Int64 Pojedynczy, podwójny
Singiel Podwójny
Typ niereferencji Typ odwołania

Klasa Type ma Get metody, które używają parametrów typu Binder do rozpoznawania odwołań do określonego elementu członkowskiego. Type.GetConstructor, Type.GetMethod i Type.GetProperty wyszukują danego członka bieżącego typu, przekazując informacje o sygnaturze dla tego członka. Binder.SelectMethod i Binder.SelectProperty są ponownie wywoływane, aby wybrać wskazane informacje o podpisach odpowiednich metod.

Zobacz także