13.6 Late Binding
Reflection can also perform late binding,
in which the application dynamically loads, instantiates, and uses a
type at runtime. This provides greater flexibility at the expense of
invocation overhead.
In this section, we create an example that uses very late binding,
dynamically discovers new types at runtime, and uses them as well.
In the next example, one or more assemblies are loaded by name (as
specified on the command line). The example then iterates through the
types in the assembly looking for subtypes of the
Greeting abstract base class. When one is found,
the type is instantiated and its SayHello( )
method invoked, which displays an appropriate greeting.
To perform the runtime discovery of types, we use an abstract base
class that's compiled into an assembly as follows
(see the source comment for filename and compilation information):
// Greeting.cs - compile with /t:library
public abstract class Greeting {
public abstract void SayHello( );
}
Compiling this code produces a file named
Greeting.dll, which the other parts of the
sample can use.
We now create a new assembly containing two concrete subtypes of the
abstract type Greeting, as follows (see the source
comment for filename and compilation information):
// English.cs - compile with /t:library /r:Greeting.dll
using System;
public class AmericanGreeting : Greeting {
private string msg = "Hey, dude. Wassup!";
public override void SayHello( ) {
Console.WriteLine(msg);
}
}
public class BritishGreeting : Greeting {
private string msg = "Good morning, old chap!";
public override void SayHello( ) {
Console.WriteLine(msg);
}
}
Compiling the source file English.cs produces a
file named English.dll, which the main program
can now dynamically reflect over and use.
Now we create the main sample, as follows (see the source comment for
filename and compilation information):
// SayHello.cs - compile with /r:Greeting.dll
// Run with SayHello.exe <dllname1> <dllname2> ... <dllnameN>
using System;
using System.Reflection;
class Test {
static void Main (string[ ] args) {
// Iterate over the cmd-line options,
// trying to load each assembly
foreach (string s in args) {
Assembly a = Assembly.LoadFrom(s);
// Pick through all the public types, looking for
// subtypes of the abstract base class Greeting
foreach (Type t in a.GetTypes( ))
if (t.IsSubclassOf(typeof(Greeting))) {
// Having found an appropriate subtype, create it
object o = Activator.CreateInstance(t);
// Retrieve the SayHello MethodInfo & invoke it
MethodInfo mi = t.GetMethod("SayHello");
mi.Invoke(o, null);
}
}
}
}
Running the sample now with SayHello English.dll
produces the following output:
Hey, dude. Wassup!
Good morning, old chap!
The interesting aspect of the preceding sample is that
it's completely late-bound; i.e., long after the
SayHello program is shipped you can create a new
type and have SayHello automatically take
advantage of it by simply specifying it on the command line. This is
one of the key benefits of late binding via reflection. You could use
this pattern to develop an application that supports third-party
plug-ins. Each plug-in would have to implement the same interface and
be stored in its own assembly. Your application could then use
Assembly.LoadFrom( ) and
Activator.CreateInstance( ) to activate the
plug-in.
13.6.1 Activation
In the previous examples, you
loaded
an assembly by hand and used the System.Activator
class to create a new instance based on a type. There are many
overrides of the CreateInstance( ) method that
provide a wide range of creation options, including the ability to
short-circuit the process and create a type directly:
object o = Activator.CreateInstance("Assem1.dll",
"Friendly.Greeting");
Other capabilities of the Activator type include
creating types on remote machines, creating types in specific
AppDomains (sandboxes), and creating types by
invoking a specific constructor (rather than using the default
constructor as these examples show).
|