Chargement et utilisation dynamiques des types

Mise à jour : novembre 2007

La réflexion fournit l'infrastructure utilisée par les compilateurs de langages tels que Microsoft Visual Basic 2005 et JScript pour implémenter la 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'à la compilation, il est appelé liaison tardive. Visual Basic 2005 permet d'utiliser la 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 d'assistance 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 (un 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 possède une méthode PrintHello qui imprime "Hello World" concaténé à du texte passé à la méthode PrintHello. La méthode PrintHello appelée dans cet exemple équivaut en fait à un Type.InvokeMember ; le code Visual Basic permet à la méthode PrintHello d'être appelée comme si le type de l'objet (helloObj) était connu au moment de la compilation (liaison anticipée) au lieu de l'être au moment de l'exécution (liaison tardive).

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

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 dont les règles de liaison 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 classe Binder assure un contrôle personnalisé de la sélection et de l'appel de membres.

Grâce à 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 les méthodes ou accéder aux champs ou propriétés de ce type. Cette technique est très utile lorsque vous ne connaissez pas le type d'un objet au moment de la compilation, par exemple lorsque le type de l'objet dépend de l'entrée d'utilisateur.

L'exemple suivant illustre un binder personnalisé simple qui ne fournit aucune conversion de type d'argument. Du code pour la création de Simple_Type.dll précède l'exemple principal. Créez Simple_Type.dll, puis incluez une référence à ce fichier dans le projet au moment de la génération.

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

Utilisez Type.InvokeMember pour appeler le membre d'un type. Les méthodes CreateInstance de diverses classes, par exemple System.Activator et System.Reflection.Assembly, sont des formes particulières de InvokeMember qui créent des instances du type spécifié. La classe Binder 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 n 1, aucune contrainte d'argument ou sélection de membre n'est nécessaire. Dans le cas n 2, seule la sélection de membre est nécessaire. Dans le cas n 3, seule la contrainte d'argument est nécessaire.

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

La résolution de surcharge est nécessaire lorsque plusieurs membres possédant 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 permet également la résolution des propriétés via les accesseurs de propriété get et set.

BindToMethod retourne le MethodBase à appeler ou une référence nulle (Nothing en Visual Basic) si aucun appel n'est possible. La valeur de retour de MethodBase n'est pas forcément contenue dans le paramètre match, même si cela est souvent le cas.

Lorsque les arguments ByRef sont présents, l'appelant a la possibilité de les retourner. Par conséquent, Binder autorise un client à mapper le tableau des arguments vers sa forme d'origine si BindToMethod a manipulé le tableau des arguments. Pour ce faire, l'appelant doit avoir la garantie que l'ordre des arguments n'a pas été modifié. Lorsque des arguments sont passés par nom, Binder réorganise le tableau d'arguments et c'est ce que voit l'appelant. 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.NonPublic est spécifié, les membres d'une accessibilité sont retournés dans l'ensemble. Si BindingFlags.NonPublic n'est pas spécifié, binder doit appliquer les règles d'accessibilité. Lors de la spécification de l'indicateur de liaison Public ou NonPublic, vous devez également spécifier l'indicateur de liaison Instance ou Static, sinon aucun membre n'est 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 n1 de l'exemple de code illustre ce point : une seule méthode PrintBob est disponible et par conséquent, aucun rappel n'est nécessaire.

Si plusieurs membres existent 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 n 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 une contrainte d'argument (conversion de type) qui convertit les arguments réels au 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 n 3 de l'exemple de code, un argument réel de type String avec la 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 se charge d'effectuer cette conversion.

ChangeType n'effectue que des contraintes étendues ou sans perte, comme l'illustre le tableau suivant.

Type de source

Type de cible

Tout type

Son type de base

Tout type

L'interface implémentée

Char

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

Unique

Double

Type non référence

Type référence

La classe Type dispose de méthodes Get qui utilisent des paramètres de type Binder pour résoudre les références à un membre particulier. Type.GetConstructor, Type.GetMethod et Type.GetProperty recherchent 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

Concepts

Affichage des informations de type

Vue d'ensemble des conversions

Référence

Type.InvokeMember

Assembly.Load