Partager via


Charger et utiliser dynamiquement des types

La réflexion fournit une infrastructure utilisée par les compilateurs de langage pour implémenter une liaison tardive implicite. La liaison est le processus de localisation de la déclaration (en d’autres termes, l’implémentation) qui correspond à un type spécifié unique. Lorsque ce processus se produit au moment de l’exécution plutôt qu’au moment de la compilation, il est appelé liaison tardive. Visual Basic vous permet d’utiliser une liaison tardive implicite dans votre code ; le compilateur Visual Basic appelle une méthode d’assistance qui utilise la réflexion pour obtenir le type d’objet. Les arguments passés à la méthode auxiliaire entraînent l'appel de la méthode appropriée au moment de l’exécution. Ces arguments sont l’instance (un objet) sur laquelle appeler la méthode, le nom de la méthode appelée (une chaîne) et les arguments passés à la méthode appelée (tableau d’objets).

Dans l’exemple suivant, le compilateur Visual Basic utilise implicitement la réflexion pour appeler une méthode sur un objet dont le type n’est pas connu au moment de la compilation. Une classe HelloWorld a une méthode PrintHello qui imprime « Hello World » concaténé à du texte passé à la méthode PrintHello. La PrintHello méthode appelée dans cet exemple est en fait une Type.InvokeMember; le code Visual Basic permet à la méthode d’être appelée comme si le type de l’objet (PrintHello) était connu au moment de la compilation (liaison anticipée) plutôt qu’au moment de l’exécution helloObj (liaison tardive).

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

Liaison personnalisée

Outre son utilisation implicite par les compilateurs, la réflexion peut également être utilisée explicitement dans le code pour exécuter la liaison tardive.

Le Common Language Runtime prend en charge plusieurs langages de programmation et les règles de liaison de ces langages diffèrent. Dans le cadre de la liaison anticipée, les générateurs de code peuvent contrôler complètement cette liaison. Cependant, dans la liaison tardive par réflexion, celle-ci doit être contrôlée par une liaison personnalisée. La Binder classe fournit un contrôle personnalisé de la sélection et de l’appel des membres.

À l’aide de la liaison personnalisée, vous pouvez charger un assembly au moment de l’exécution, obtenir des informations sur les types de cet assembly, spécifier le type souhaité, puis appeler des méthodes ou accéder à des champs ou des propriétés sur ce type. Cette technique est utile si vous ne connaissez pas le type d’un objet au moment de la compilation, par exemple lorsque le type d’objet dépend de l’entrée utilisateur.

L’exemple suivant illustre un classeur personnalisé simple qui ne fournit aucune conversion de type d’argument. Le code pour Simple_Type.dll précède l’exemple principal. Veillez à générer Simple_Type.dll, puis incluez une référence dans le projet lors de la compilation.

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

Permet Type.InvokeMember d’appeler un membre d’un type. Les CreateInstance méthodes de différentes classes, telles que Activator.CreateInstance et Assembly.CreateInstance, sont des formes spécialisées de InvokeMember qui créent de nouvelles instances du type spécifié. La Binder classe est utilisée pour la résolution de surcharge et la contrainte d’argument dans ces méthodes.

L’exemple suivant montre les trois combinaisons possibles de contrainte d’argument (conversion de type) et de sélection de membre. Dans le cas 1, aucune contrainte d’argument ou sélection de membre n’est nécessaire. Dans le cas 2, seule la sélection de membres est nécessaire. Dans le cas 3, seule la coercition d’argument est nécessaire.

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

La résolution de surcharge est nécessaire lorsque plusieurs membres portant le même nom sont disponibles. Les méthodes Binder.BindToMethod et Binder.BindToField permettent de résoudre la liaison à un seul membre. Binder.BindToMethod fournit également la résolution des propriétés par le biais des accesseurs de propriétés get et set.

BindToMethod retourne l’appel MethodBase , ou une référence Null (Nothing en Visual Basic) si aucun appel de ce type n’est possible. La MethodBase valeur de retour n’a pas besoin d’être l’une de celles contenues dans le paramètre de correspondance , même si c’est le cas habituel.

Lorsque des arguments ByRef sont présents, l’appelant peut vouloir les récupérer. Par conséquent, Binder permet à un client de mapper le tableau d’arguments à son formulaire d’origine s’il BindToMethod a manipulé le tableau d’arguments. Pour ce faire, l’appelant doit être garanti que l’ordre des arguments n’est pas modifié. Lorsque les arguments sont passés par nom, Binder réorganise le tableau d’arguments et c’est ce que l’appelant voit. Pour plus d’informations, consultez Binder.ReorderArgumentArray.

L’ensemble des membres disponibles correspond aux membres définis dans le type ou n’importe quel type de base. Si BindingFlags est spécifié, les membres d’une accessibilité quelconque sont retournés dans l’ensemble. Si BindingFlags.NonPublic n’est pas spécifié, le binder doit appliquer les règles d’accessibilité. Lorsque vous spécifiez l’indicateur de liaison Public ou NonPublic, vous devez également spécifier l’indicateur de liaison Instance ou Static, ou aucun membre ne sera retourné.

S’il n’existe qu’un seul membre avec le nom spécifié, aucun rappel n’est nécessaire, et la liaison s’effectue sur cette méthode. Le cas 1 de l’exemple de code illustre ce point : une PrintBob seule méthode est disponible et, par conséquent, aucun rappel n’est nécessaire.

S’il existe plusieurs membres dans l’ensemble disponible, toutes ces méthodes sont passées à BindToMethod, qui sélectionne la méthode appropriée et la retourne. Dans le cas 2 de l’exemple de code, il existe deux méthodes nommées PrintValue. La méthode appropriée est sélectionnée par l’appel à BindToMethod.

ChangeType effectue la contrainte d’argument (conversion de type), qui convertit les arguments réels en type des arguments formels de la méthode sélectionnée. ChangeType est appelé pour chaque argument même si les types correspondent exactement.

Dans le cas 3 de l’exemple de code, un argument de type String réel avec une valeur « 5.5 » est passé à une méthode avec un argument formel de type Double. Pour que l’appel réussisse, la valeur de chaîne « 5.5 » doit être convertie en valeur double. ChangeType effectue cette conversion.

ChangeType effectue uniquement des forçages de type étendus ou sans perte, comme illustré dans le tableau suivant.

Type de source Type de cible
Tout type Son type de base
Tout type Interface qu’il implémente
Carboniser UInt16, UInt32, Int32, UInt64, Int64, Single, Double
Octet 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
Célibataire Double
Type non référence Type de référence

La classe Type possède des Get méthodes qui utilisent des paramètres de type Binder pour résoudre les références à un membre particulier. Type.GetConstructor, Type.GetMethodet Type.GetProperty recherchez un membre particulier du type actuel en fournissant des informations de signature pour ce membre. Binder.SelectMethod et Binder.SelectProperty sont rappelés pour sélectionner les informations de signature données des méthodes appropriées.

Voir aussi