- Reflection on Types
- Getting Started with C# Reflection
- More Advanced Uses of C# Reflection
- Reflection and Loose Coupling: Late Binding
- Conclusion
Reflection and Loose Coupling: Late Binding
With late binding, we want to be able to create and activate a given object on demand, without having any substantial compile-time knowledge of its type. The easiest way to model this programming situation is to use a prepackaged .NET assembly, and then try to extract and instantiate a constituent class. This process sounds really difficult, but it isn't.
For this example, I'll use an assembly that I created previously. This assembly contains a single class with one method, as illustrated in Listing 8.
Listing 8An assembly class library.
namespace ClassLibrary1 { public class Class1 { public void dumpPlatformDetails() { String nl = Environment.NewLine; System.Console.WriteLine("HasShutdownStarted: " + Environment.HasShutdownStarted); System.Console.WriteLine("MachineName: " + Environment.MachineName); System.Console.WriteLine("OSVersion: " + Environment.OSVersion.ToString()); System.Console.WriteLine("TickCount: " + Environment.TickCount); System.Console.WriteLine("UserDomainName: " + Environment.UserDomainName); System.Console.WriteLine("UserInteractive: " + Environment.UserInteractive); System.Console.WriteLine("UserName: " + Environment.UserName); System.Console.WriteLine("OS Version: " + Environment.Version.ToString()); System.Console.WriteLine("WorkingSet: " + Environment.WorkingSet); String[] drives = Environment.GetLogicalDrives(); System.Console.WriteLine("Logical Drives: " + String.Join(", ", drives)); } } }
If you haven't dealt with assemblies before this point, don't worry. A .NET assembly replaces the old dynamic link library (DLL) model, and is simply a means of packaging classes for use by client code. When you want to access code in a .NET assembly via C#, you simply add a using statement and then a project reference to the assembly.
We want to be able to use the assembly-hosted code in Listing 8. How do we go about it? The first task is to load the assembly, as in Listing 9.
Listing 9Loading an assembly into memory.
Assembly assembly = null; try { assembly = Assembly.Load("ClassLibrary1"); } catch (FileNotFoundException e) { Console.WriteLine("Exception {0} ", e.Message); }
Once the assembly is loaded into memory, you can start to use the symbols it exports. For the code in Listing 9 to work, the assembly file, called ClassLibrary1.dll, must be placed in the Debug folder of the current project. (This is already done in the supplied source code, so you don't have to do anything.)
At this stage, we have the assembly in memory. How do we get at the exported symbols? Easily, as Listing 10 shows.
Listing 10Pulling Symbols Out of the Assembly.
String targetAssemblyStr = "ClassLibrary1"; String targetAssemblySymbol = "Class1"; String targetAssemblyMethod = "dumpPlatformDetails"; Type anExtractedType = assembly.GetType(targetAssemblyStr + "." + targetAssemblySymbol); object reconstitutedObject = Activator.CreateInstance(anExtractedType); MethodInfo methodInfo1 = anExtractedType.GetMethod(targetAssemblyMethod); methodInfo1.Invoke(reconstitutedObject, null);
In Listing 10, after defining three string constants, the next important thing I do is to get a type from the assembly, using the following line:
Type anExtractedType = assembly.GetType("ClassLibrary1.Class1");
In this line, I've replaced the string constants with their literal values, just to make it easier to read. Now that we have an extracted type, let's create an instance of this type with the following statement:
object reconstitutedObject = Activator.CreateInstance(anExtractedType);
The static method Activator.CreateInstance returns a normal C# object. We're nearly there now! How do we determine and then invoke a method on the returned object? Again, no big deal, as illustrated by the following line:
MethodInfo methodInfo1 = anExtractedType.GetMethod("dumpPlatformDetails"); methodInfo1.Invoke(reconstitutedObject, null);
In the first line, we create an instance of MethodInfo based on the signature name "dumpPlatformDetails". We now have enough information to invoke the method, and we get the output shown in Listing 11.
Listing 11The assembly code output.
HasShutdownStarted: False MachineName: MYMACHINE OSVersion: Microsoft Windows NT 5.1.2600 Service Pack 3 TickCount: 5936093 UserDomainName: MYMACHINE UserInteractive: True UserName: Stephen OS Version: 2.0.50727.3082 WorkingSet: 9138176 Logical Drives: C:\, D:\, E:\
Listing 11 illustrates a successful invocation of the method dumpPlatformDetails() from the assembly.
So that's how you can use reflection to reconstitute an object from within an assembly. The example I used hardcoded the required method signature. However, this isn't necessary, because you can also use reflection to extract the method names programmatically as per Listing 5. You can also do something similar to extract any required parameters, with code like this:
anExtractedType.GetGenericArguments();
This means that no hardcoded method or parameter strings are required in your code.
Why would you want to go to all this trouble in the first place? In other words, what are the cases for using reflection?
- One example of when you might need to use reflection is if you have access only to assemblies; that is, you don't have the original source code. For cases such as this, you can use the techniques described earlier to determine the required code details.
- An organization might package all of its .NET code in the form of assemblies. This is a common way to distribute code, even within an organization. Rather than publishing the API details, client code could use reflection to determine the interfaces. An obvious use for this technique might be in implementing versioning of a given class.