Dynamically Loading and Using Types
Reflection provides infrastructure used by language compilers such as Microsoft Visual Basic 2005 and JScript to implement implicit late binding. Binding is the process of locating the declaration (that is, the implementation) that corresponds to a uniquely specified type. When this process occurs at run time rather than at compile time, it is called late binding. Visual Basic 2005 allows you to use implicit late binding in your code; the Visual Basic compiler calls a helper method that uses reflection to obtain the object type. The arguments passed to the helper method cause the appropriate method to be invoked at run time. These arguments are the instance (an object) on which to invoke the method, the name of the invoked method (a string), and the arguments passed to the invoked method (an array of objects).
In the following example, the Visual Basic compiler uses reflection implicitly to call a method on an object whose type is not known at compile time. A HelloWorld class has a PrintHello method that prints out "Hello World" concatenated with some text that is passed to the PrintHello method. The PrintHello method called in this example is actually a Type.InvokeMember; the Visual Basic code allows the PrintHello method to be invoked as if the type of the object (helloObj) were known at compile time (early binding) rather than at run time (late binding).
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
Custom Binding
In addition to being used implicitly by compilers for late binding, reflection can be used explicitly in code to accomplish late binding.
The common language runtime supports multiple programming languages, and the binding rules of these languages differ. In the early-bound case, code generators can completely control this binding. However, in late binding through reflection, binding must be controlled by customized binding. The Binder class provides custom control of member selection and invocation.
Using custom binding, you can load an assembly at run time, obtain information about types in that assembly, specify the type that you want, and then invoke methods or access fields or properties on that type. This technique is useful if you do not know an object's type at compile time, such as when the object type is dependent on user input.
The following example demonstrates a simple custom binder that provides no argument type conversion. Code for Simple_Type.dll
precedes the main example. Be sure to build Simple_Type.dll
and then include a reference to it in the project at build time.
' 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 and CreateInstance
Use Type.InvokeMember to invoke a member of a type. The CreateInstance methods of various classes, such as System.Activator and System.Reflection.Assembly, are specialized forms of InvokeMember that create new instances of the specified type. The Binder class is used for overload resolution and argument coercion in these methods.
The following example shows the three possible combinations of argument coercion (type conversion) and member selection. In Case 1, no argument coercion or member selection is needed. In Case 2, only member selection is needed. In Case 3, only argument coercion is needed.
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);
}
}
Overload resolution is needed when more than one member with the same name is available. The Binder.BindToMethod and Binder.BindToField methods are used to resolve binding to a single member. Binder.BindToMethod also provides property resolution through the get and set property accessors.
BindToMethod returns the MethodBase to invoke, or a null reference (Nothing in Visual Basic) if no such invocation is possible. The MethodBase return value need not be one of those contained in the match parameter, although that is the usual case.
When ByRef arguments are present, the caller might want to get them back. Therefore, Binder allows a client to map the array of arguments back to its original form if BindToMethod has manipulated the argument array. In order to do this, the caller must be guaranteed that the order of the arguments is unchanged. When arguments are passed by name, Binder reorders the argument array, and that is what the caller sees. For more information, see Binder.ReorderArgumentArray Method.
The set of available members are those members defined in the type or any base type. If BindingFlags.NonPublic is specified, members of any accessibility will be returned in the set. If BindingFlags.NonPublic is not specified, the binder must enforce accessibility rules. When specifying the Public or NonPublic binding flag, you must also specify the Instance or Static binding flag, or no members will be returned.
If there is only one member of the given name, no callback is necessary, and binding is done on that method. Case 1 of the code example illustrates this point: Only one PrintBob method is available, and therefore no callback is needed.
If there is more than one member in the available set, all these methods are passed to BindToMethod, which selects the appropriate method and returns it. In Case 2 of the code example, there are two methods named PrintValue. The appropriate method is selected by the call to BindToMethod.
ChangeType performs argument coercion (type conversion), which converts the actual arguments to the type of the formal arguments of the selected method. ChangeType is called for every argument even if the types match exactly.
In Case 3 of the code example, an actual argument of type String with a value of "5.5" is passed to a method with a formal argument of type Double. For the invocation to succeed, the string value "5.5" must be converted to a double value. ChangeType performs this conversion.
ChangeType performs only lossless or widening coercions, as shown in the following table.
Source type | Target type |
---|---|
Any type |
Its base type |
Any type |
Interface it implements |
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 |
Nonreference type |
Reference type |
The Type class has Get methods that use parameters of type Binder to resolve references to a particular member. Type.GetConstructor, Type.GetMethod, and Type.GetProperty search for a particular member of the current type by providing signature information for that member. Binder.SelectMethod and Binder.SelectProperty are called back on to select the given signature information of the appropriate methods.
See Also
Reference
Dynamically Loading and Using Types
Type.InvokeMember
Assembly.Load