In addition to being used implicitly by compilers for late binding, reflection can be used explicitly in code to accomplish late binding.
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.
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.
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.