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