Compartir a través de


Cargar y utilizar tipos dinámicamente

La reflexión proporciona la infraestructura utilizada por los compiladores de lenguaje como Microsoft Visual Basic 2005 y JScript para implementar el enlace implícito en tiempo de ejecución. El enlace es el proceso de buscar la declaración (es decir, la implementación) que corresponde a un tipo especificado de manera exclusiva. Cuando este proceso se produce en tiempo de ejecución en lugar de en tiempo de compilación, se denomina enlace en tiempo de ejecución. Visual Basic 2005 permite utilizar el enlace en tiempo de ejecución implícito en el código; el compilador de Visual Basic llama a un método auxiliar que utiliza la reflexión para obtener el tipo del objeto. Los argumentos que se pasan al método auxiliar hacen que se invoque el método apropiado en tiempo de ejecución. Dichos argumentos son la instancia (un objeto) en la que se va a invocar al método, al nombre del método invocado (una cadena) y a los argumentos pasados al método invocado (una matriz de objetos).

En el ejemplo siguiente, el compilador de Visual Basic utiliza implícitamente la reflexión para llamar a un método en un objeto cuyo tipo se desconoce en tiempo de compilación. Una clase HelloWorld tiene un método PrintHello que imprime "Hello World" de forma concatenada con texto que se pasa al método PrintHello. El método PrintHello al que se llama en este ejemplo es en realidad un método Type.InvokeMember; el código de Visual Basic permite la invocación del método PrintHello como si se conociese el tipo del objeto (helloObj) en tiempo de compilación (enlace en tiempo de compilación) y no en tiempo de ejecución (enlace en tiempo de ejecución).

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

Enlace personalizado

Además de utilizarla implícitamente los compiladores para el enlace en tiempo de ejecución, la reflexión puede utilizarse explícitamente en código para realizar el enlace en tiempo de ejecución.

Common Language Runtime admite diversos lenguajes de programación y las reglas de enlace de dichos lenguajes varían. En el caso del enlace en tiempo de compilación, los generadores de código pueden controlar completamente este enlace. Sin embargo, en el caso del enlace en tiempo de ejecución mediante reflexión, se ha de controlar el enlace mediante enlace personalizado. La clase Binder proporciona un control personalizado de la selección e invocación de miembros.

Mediante el enlace personalizado se puede cargar un ensamblado en tiempo de ejecución, obtener información sobre los tipos de dicho ensamblado, especificar el tipo que se desea y, a continuación, invocar a métodos u obtener acceso a campos o propiedades de dicho tipo. Esta técnica resulta útil si se desconoce el tipo de un objeto en tiempo de compilación, como cuando el tipo de objeto depende de los datos proporcionados por el usuario.

En el ejemplo siguiente se muestra un enlazador simple personalizado que no proporciona ninguna conversión de tipo de argumento. El código para Simple_Type.dll va antes del ejemplo principal. Asegúrese de que crea Simple_Type.dll y de que incluye, a continuación, una referencia a este enlazador en el proyecto en tiempo de generación.

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

Se utiliza Type.InvokeMember para invocar a un miembro de un tipo. Los métodos CreateInstance de varias clases, como System.Activator y System.Reflection.Assembly, son formas especializadas de InvokeMember que crean nuevas instancias del tipo especificado. La clase Binder se utiliza para la resolución de sobrecarga y la conversión de argumentos en estos métodos.

En el ejemplo siguiente se muestran las tres posibles combinaciones de conversión de argumentos (conversión de tipos) y selección de miembros. En el primer caso (Case 1), no se precisa ninguna conversión de argumentos o selección de miembros. En el segundo caso (Case 2), sólo se precisa la selección de miembros. En el tercer caso (Case 3), sólo se precisa la conversión de argumentos.

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

Se precisa la resolución de sobrecarga cuando hay más de un miembro con el mismo nombre. Se utilizan los métodos Binder.BindToMethod y Binder.BindToField para resolver el enlace en un solo miembro. Binder.BindToMethod también proporciona resolución de propiedades mediante los descriptores de acceso de propiedades get y set.

BindToMethod devuelve la clase MethodBase que hay que invocar o una referencia nula (Nothing en Visual Basic) si la invocación no es posible. El valor devuelto de MethodBase no tiene que ser uno de los valores que contiene el parámetro match, si bien suele ser el caso.

Cuando hay argumentos ByRef, es posible que el llamador desee que se le devuelvan. Por tanto, Binder permite a un cliente asignar de nuevo la matriz de argumentos a su formulario original si BindToMethod ha manipulado la matriz de argumentos. Para ello, es preciso garantizar al llamador que no se modifica el orden de los argumentos. Cuando se pasan argumentos por nombre, Binder reorganiza la matriz de argumentos y eso es lo que ve el llamador. Para obtener más información, vea Binder.ReorderArgumentArray (Método).

El conjunto de miembros disponibles son los miembros definidos en el tipo o cualquier tipo base. Si se especifica BindingFlags.NonPublic, se devolverán los miembros de cualquier accesibilidad del conjunto. Si no se especifica BindingFlags.NonPublic, el enlazador deberá imponer las reglas de accesibilidad. Cuando se especifica el indicador de enlace Public o NonPublic, también se debe especificar el indicador de enlace Instance o Static; en caso contrario, no se devolverá ningún miembro.

Si sólo hay un miembro con el nombre especificado, no es necesaria la devolución de llamada y el enlace se realiza en dicho método. En el primer caso (Case 1) del ejemplo de código se ilustra este punto: hay sólo un método PrintBob disponible, por lo que no es necesario devolver la llamada.

Si hay más de un miembro en el conjunto disponible, todos estos métodos se pasan a BindToMethod, que selecciona el método apropiado y lo devuelve. En el segundo caso (Case 2) del ejemplo de código, hay dos métodos denominados PrintValue. El método apropiado se selecciona al llamar a BindToMethod.

ChangeType lleva a cabo una conversión de argumentos (conversión de tipos), que convierte los argumentos reales al tipo de los argumentos formales del método seleccionado. Se llama a ChangeType para cada argumento, incluso si los tipos son exactamente iguales.

En el tercer caso (Case 3) del ejemplo de código, se pasa un argumento real de tipo String con un valor de "5.5" a un método con un argumento formal de tipo Double. Para que la invocación se realice correctamente, el valor de cadena "5.5" debe convertirse a un valor de tipo double. ChangeType lleva a cabo esta conversión.

ChangeType realiza sólo conversiones de ampliación o sin pérdidas, tal como se muestra en la siguiente tabla.

Tipo de origen Tipo de destino

Cualquier tipo

El tipo base correspondiente

Cualquier tipo

Interfaz que implementa

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

Tipo de no referencia

Tipo de referencia

La clase Type tiene métodos Get que utilizan parámetros de tipo Binder para resolver las referencias en un miembro determinado. Type.GetConstructor, Type.GetMethod y Type.GetProperty buscan un miembro determinado del tipo actual proporcionando información de prototipo correspondiente a dicho miembro. Se devuelve la llamada a Binder.SelectMethod y Binder.SelectProperty para seleccionar la información de prototipo especificada de los métodos apropiados.

Vea también

Referencia

Dynamically Loading and Using Types
Type.InvokeMember
Assembly.Load

Conceptos

Ver información de tipos
Información general sobre conversión