Compartilhar via


Carregue e use tipos dinamicamente

A reflexão fornece a infraestrutura usada pelos compiladores de linguagem para implementar a associação tardia implícita. Associação é o processo de localizar a declaração (ou seja, a implementação) que corresponde a um tipo especificado exclusivamente. Quando esse processo ocorre em tempo de execução em vez de em tempo de compilação, ele é chamado de associação tardia. O Visual Basic permite que você use a associação tardia implícita em seu código; o compilador do Visual Basic chama um método auxiliar que usa reflexão para obter o tipo de objeto. Os argumentos passados para o método auxiliar fazem com que o método apropriado seja invocado em tempo de execução. Esses argumentos são a instância (um objeto) na qual invocar o método, o nome do método invocado (uma cadeia de caracteres) e os argumentos passados para o método invocado (uma matriz de objetos).

No exemplo a seguir, o compilador do Visual Basic usa reflexão implicitamente para chamar um método em um objeto cujo tipo não é conhecido em tempo de compilação. Uma HelloWorld classe tem um PrintHello método que imprime "Hello World" concatenado com algum texto passado para o PrintHello método. O PrintHello método chamado neste exemplo é, na verdade, um Type.InvokeMember; o código do Visual Basic permite que o PrintHello método seja invocado como se o tipo do objeto (helloObj) fosse conhecido no tempo de compilação (associação antecipada) em vez de em tempo de execução (associação tardia).

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

Vinculação personalizada

Além de ser usada implicitamente por compiladores para associação tardia, a reflexão pode ser usada explicitamente no código para realizar a associação tardia.

O common language runtime dá suporte a várias linguagens de programação e as regras de associação desses idiomas diferem. No caso de associação antecipada, os geradores de código podem controlar completamente esse vínculo. No entanto, na vinculação tardia por meio da reflexão, a associação deve ser controlada pela vinculação personalizada. A Binder classe fornece controle personalizado da seleção e invocação de membros.

Usando a associação personalizada, você pode carregar um assembly em tempo de execução, obter informações sobre tipos nesse assembly, especificar o tipo desejado e, em seguida, invocar métodos ou acessar campos ou propriedades nesse tipo. Essa técnica será útil se você não souber o tipo de um objeto no tempo de compilação, como quando o tipo de objeto depende da entrada do usuário.

O exemplo a seguir demonstra um associador personalizado simples que não fornece conversão de tipo de argumento. O código para Simple_Type.dll precede o exemplo principal. Certifique-se de compilar Simple_Type.dll e, em seguida, incluir uma referência a ele no projeto no momento da compilação.

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

Use Type.InvokeMember para invocar um membro de um tipo. Os métodos CreateInstance de várias classes, como Activator.CreateInstance e Assembly.CreateInstance, são formas especializadas de InvokeMember que criam novas instâncias do tipo especificado. A Binder classe é usada para resolução de sobrecarga e coerção de argumento nesses métodos.

O exemplo a seguir mostra as três combinações possíveis de coerção de argumento (conversão de tipo) e seleção de membro. No Caso 1, nenhuma coerção de argumento ou seleção de membro é necessária. No Caso 2, somente a seleção de membro é necessária. No Caso 3, somente a coerção de argumento é necessária.

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

A resolução de sobrecarga é necessária quando mais de um membro com o mesmo nome está disponível. Os Binder.BindToMethod métodos e os métodos Binder.BindToField são usados para resolver a associação a um único membro. Binder.BindToMethod também fornece resolução de propriedades por meio dos acessadores get e set.

BindToMethod retorna MethodBase a ser invocado, ou uma referência nula (Nothing no Visual Basic) caso tal invocação não seja possível. O MethodBase valor retornado não precisa ser um dos contidos no parâmetro de correspondência , embora esse seja o caso comum.

Quando os argumentos ByRef estiverem presentes, talvez o chamador queira recuperá-los. Portanto, Binder permite que um cliente mapeie a matriz de argumentos de volta ao formulário original se BindToMethod tiver manipulado a matriz de argumentos. Para fazer isso, o chamador deve ter a garantia de que a ordem dos argumentos está inalterada. Quando os argumentos são passados pelo nome, Binder reordena a matriz de argumentos e é isso que o chamador vê. Para obter mais informações, consulte Binder.ReorderArgumentArray.

O conjunto de membros disponíveis são os membros definidos no tipo ou em qualquer tipo base. Se BindingFlags estiver especificado, os membros de qualquer acessibilidade serão retornados no conjunto. Se BindingFlags.NonPublic não for especificado, o vinculador deverá impor regras de acessibilidade. Ao especificar o sinalizador de associação Public ou NonPublic, você também deve especificar o sinalizador de associação Instance ou Static, ou nenhum membro será retornado.

Se houver apenas um membro do nome fornecido, nenhum retorno de chamada será necessário e a associação é realizada nesse método. O caso 1 do exemplo de código ilustra este ponto: apenas um PrintBob método está disponível e, portanto, nenhum retorno de chamada é necessário.

Se houver mais de um membro no conjunto disponível, todos esses métodos serão passados para BindToMethod, o que seleciona o método apropriado e o retorna. No caso 2 do exemplo de código, há dois métodos chamados PrintValue. O método apropriado é selecionado pela chamada para BindToMethod.

ChangeType executa a coerção de argumento (conversão de tipo), que converte os argumentos reais para o tipo de argumentos formais do método selecionado. ChangeType é chamado para cada argumento, mesmo que os tipos correspondam exatamente.

No Caso 3 do exemplo de código, um argumento real do tipo String com um valor "5.5" é passado para um método com um argumento formal do tipo Double. Para que a invocação seja bem-sucedida, o valor da cadeia de caracteres "5.5" deve ser convertido em um valor duplo. ChangeType executa essa conversão.

ChangeType executa apenas coerções sem perda ou ampliação, conforme mostrado na tabela a seguir.

Tipo de origem Tipo de alvo
Qualquer tipo Seu tipo base
Qualquer tipo Interface que implementa
Caractere UInt16, UInt32, Int32, UInt64, Int64, Single e Double
byte Char, UInt16, Int16, UInt32, Int32, UInt64, Int64, Single e Double
SByte Int16, Int32, Int64, Single e Double
UInt16 UInt32, Int32, UInt64, Int64, Single e Double
Int16 Int32, Int64, Single e Double
UInt32 UInt64, Int64, Single e Double
Int32 Int64, Single e Double
UInt64 Single e Double
Int64 Single e Double
Solteiro Duplo
Tipo de não-referência Tipo de referência

A Type classe tem Get métodos que usam parâmetros de tipo Binder para resolver referências a um membro específico. Type.GetConstructor, Type.GetMethod e Type.GetProperty buscam um membro específico do tipo atual fornecendo informações de assinatura para esse membro. Binder.SelectMethod e Binder.SelectProperty são chamados novamente para selecionar as informações de assinatura fornecidas dos métodos apropriados.

Consulte também