Compartir a través de


Cargue y use tipos de forma dinámica

La reflexión proporciona la infraestructura que los compiladores de lenguaje utilizan para implementar el enlace tardío implícito. La vinculación es el proceso de localizar la declaración (es decir, la implementación) que corresponde a un tipo especificado de manera única. Cuando este proceso se produce en tiempo de ejecución en lugar de en tiempo de compilación, se denomina ligadura tardía. Visual Basic le permite usar el enlace tardío implícito en su código; el compilador de Visual Basic llama a un método auxiliar que utiliza la reflexión para obtener el tipo de objeto. Los argumentos pasados al método auxiliar hacen que el método adecuado se invoque en tiempo de ejecución. Estos argumentos son la instancia (un objeto) en la que se va a invocar el método, el nombre del método invocado (una cadena) y los argumentos pasados al método invocado (una matriz de objetos).

En el ejemplo siguiente, el compilador de Visual Basic usa la reflexión implícitamente para llamar a un método en un objeto cuyo tipo no se conoce en tiempo de compilación. Una HelloWorld clase tiene un PrintHello método que imprime "Hola mundo" concatenado con algún texto que se pasa al PrintHello método . El PrintHello método llamado en este ejemplo es realmente un Type.InvokeMember; el código de Visual Basic permite invocar el PrintHello método como si el tipo del objeto (helloObj) se conociera en tiempo de compilación (enlace anticipado) en lugar de en tiempo de ejecución (enlace tardío).

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

Vinculación personalizada

Además de ser usados implícitamente por los compiladores para el enlace tardío, la reflexión se puede usar explícitamente en el código para realizar el enlace tardío.

Common Language Runtime admite varios lenguajes de programación y las reglas de enlace de estos lenguajes difieren. En el caso de enlace temprano, los generadores de código pueden controlar completamente esta vinculación. Sin embargo, en la vinculación tardía a través de la reflexión, la vinculación debe controlarse mediante una vinculación personalizada. La Binder clase proporciona control personalizado de la selección e invocación de miembros.

Con el enlace personalizado, puede cargar un ensamblado en tiempo de ejecución, obtener información sobre los tipos de ese ensamblado, especificar el tipo que desee y, a continuación, invocar métodos o tener acceso a campos o propiedades en ese tipo. Esta técnica es útil si no conoce el tipo de un objeto en tiempo de compilación, como cuando el tipo de objeto depende de la entrada del usuario.

En el ejemplo siguiente se muestra un enlazador personalizado sencillo que no proporciona ninguna conversión de tipo de argumento. El código de Simple_Type.dll precede al ejemplo principal. Asegúrese de compilar Simple_Type.dll y, a continuación, incluir una referencia a él en el proyecto en tiempo de compilación.

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

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

En el ejemplo siguiente se muestran las tres posibles combinaciones de coerción de argumentos (conversión de tipos) y selección de miembros. En el caso 1, no se necesita coerción de argumentos ni selección de miembros. En el caso 2, solo se necesita la selección de miembros. En el caso 3, solo se necesita la coerción de argumentos.

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 resolución de sobrecarga es necesaria cuando hay más de un miembro con el mismo nombre disponible. Los métodos Binder.BindToMethod y Binder.BindToField se usan para resolver el enlace a un único miembro. Binder.BindToMethod también proporciona resolución de propiedades a través de los accesores de propiedades get y set.

BindToMethod devuelve el objeto MethodBase que se va a invocar o una referencia nula (Nothing en Visual Basic) si no es posible dicha invocación. El MethodBase valor devuelto no debe ser uno de los contenidos en el parámetro match , aunque es el caso habitual.

Cuando los argumentos ByRef están presentes, es posible que el autor de la llamada quiera recuperarlos. Por lo tanto, Binder permite que un cliente asigne la matriz de argumentos a su forma original si BindToMethod ha manipulado la matriz de argumentos. Para ello, se debe garantizar al autor de la llamada que no cambia el orden de los argumentos. Cuando los argumentos se pasan por nombre, Binder reordena la matriz de argumentos y eso es lo que ve el autor de la llamada. Para obtener más información, consulte Binder.ReorderArgumentArray.

El conjunto de miembros disponibles es el de los miembros definidos en el tipo o en cualquiera de los tipos base. Si BindingFlags se especifica , los miembros de cualquier accesibilidad se devuelven en el conjunto. Si BindingFlags.NonPublic no se especifica, el enlazador debe aplicar reglas de accesibilidad. Al especificar el indicador de enlace Public o NonPublic, también debe especificar el indicador de enlace Instance o Static, o no se devolverá ningún miembro.

Si solo hay un miembro con el nombre especificado, no es necesario realizar ningún callback, y la asociación se realiza en ese método. El caso 1 del ejemplo de código ilustra este punto: solo hay un método PrintBob disponible y, por lo tanto, no se necesita un callback.

Si hay más de un miembro en el conjunto disponible, todos estos métodos se pasan a BindToMethod, que selecciona el método adecuado y lo devuelve. En el caso 2 del ejemplo de código, hay dos métodos denominados PrintValue. El método adecuado es seleccionado por la llamada a BindToMethod.

ChangeType realiza la coerción de argumentos (conversión de tipos), que convierte los argumentos reales en el tipo de los argumentos formales del método seleccionado. ChangeType se llama para cada argumento incluso si los tipos coinciden exactamente.

En el caso 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 en un valor doble. ChangeType realiza esta conversión.

ChangeType realiza solo coerciones sin pérdida o de ampliación, como se muestra en la tabla siguiente.

Tipo de origen Tipo de destino
Cualquier tipo Su tipo base
Cualquier tipo Interfaz que implementa
Carbón 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
Soltero Doble
Tipo de no referencia Tipo de referencia

La Type clase tiene Get métodos que usan parámetros de tipo Binder para resolver referencias a un miembro determinado. Type.GetConstructor, Type.GetMethod, y Type.GetProperty buscan un miembro específico del tipo actual proporcionando información sobre la firma de ese miembro. Binder.SelectMethod y Binder.SelectProperty se vuelven a llamar para seleccionar la información de firma dada de los métodos adecuados.

Consulte también