Home > Articles > Programming > C#

  • Print
  • + Share This
Like this article? We recommend Reflection and Loose Coupling: Late Binding

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 8—An 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 9—Loading 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 10—Pulling 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 11—The 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.
  • + Share This
  • 🔖 Save To Your Account