Harness the Power of Reflection: Creating an Object Factory
by Jon Wojtowicz

 

Reflection

Reflection is the mechanism of discovering class information solely at run time. Reflection is a powerful feature in .Net. Basically it allows your code to look back at classes, objects, methods, properties and events at run time. .Net has made these features easy to use API in the System.Relection namespace.
You may ask why this would be useful. Have you ever seen the custom configuration section handler commonly used? Have you ever wondered why this mechanism works? Well, I'm going to walk through some code that shows the basics of dynamically creating objects based on a particular string format used by .Net.


Type Information Strings

In .Net, Microsoft uses a common pattern for specifying type in configuration files. The format is "[ClassName], [AssemblyName]". This allows you to load types that are not in any of the executing assemblies. The only requirement is that the referenced assemblies be in the GAC or in one of the probing paths.
Why is this string so powerful? It specifies all the information you need to create an object of the specified type at runtime. By using a traditional factory pattern of interfaces with classes that implement the interface, you can change the type of object performing a task by simply editing your configuration file. No need to add a new reference and edit your code. By simply adding the assembly in a path your application can find with it's supporting assemblies and editing a string you can change the behavior of your application. Now that's power!

The Code

The code to create the objects is actually incredibly simple. Just think all this power and simplicity also, what a deal.
The first part is to split the string into the class name and assembly name. This is accomplished with these three lines of code finding the first comma:
int commaIndex = typeString.IndexOf(",");
string className = typeString.Substring(0, commaIndex).Trim();
string assemblyName = typeString.Substring(commaIndex + 1).Trim();
Now that we have the type and assembly names we can start to load our type. To do this we use two methods of the Assembly class, Load and LoadWithPartialName. We first try to load the assembly assuming we have a full assembly name of AssemblyName, Version=1.0.0.23, Culture=neutral, PublicKeyToken=null. If the name given is not a full name, then the Load method may fail. We than try a second time using the LoadWithPartialName. This will give us two chances to resolve the assembly. What if we are trying to load an assembly that is already loaded? Nothing, the loader will check to see if the assembly is already loaded, if it is it gets a reference to the loaded assembly.The code for this section is as follows:
Assembly assembly = null;
try
{
assembly = Assembly.Load(assemblyName);
}
catch
{
try
{
assembly = Assembly.LoadWithPartialName(assemblyName);
}
catch
{
throw new ArgumentException("Can't load assembly " + assemblyName);
}
}
We then simply call GetType on the assembly passing in our class string as the first argument. The second argument is whether it should throw an exception if it fails to load the type. I also set this to false because I perform a test to see if the type is null rather than relying on exceptions for performance reasons. The third argument is whether to ignore case. I generally set this to false for performance reasons but this can fail if someone gets the wrong casing for the type name.
return assembly.GetType(className, false, false);
Now we have the type that we are looking for. We need to get a constructor to actually perform the instantiation. We can do this quite simply by calling GetConstructor on our type. To determine the constructor we want, we need to supply an array of types that match the parameters for the constructor we want. In this case we want the default constructor so we create an empty array of type. We use the simplest GetConstructor call and simply pass in the type array. This will search all the constructors for a match disregarding all other attributes of the constructor. The code for this invocation is the following:
Type[] types = new Type[0];
ConstructorInfo info = targetType.GetConstructor(types);
Now all we have to do is call Invoke on our constructor info class to instantiate the object. We also need to check for a null return value which will indicate a problem instantiating the type. The code for this is the following
object targetObject = info.Invoke(null);
Pulling all this code together we have the following. I have broken this into two methods, one to resolve the type and the main method which instantiates and returns the object. (For all the VB people I've included a VB version of the code)
C#

public sealed class ObjectFactory
{
private ObjectFactory()
{}
public static object Create(string typeName)
{
// resolve the type
Type targetType = ResolveType(typeName);
if(targetType == null)
throw new ArgumentException("Can't load type " + typeName);

// get the default constructor and instantiate
Type[] types = new Type[0];
ConstructorInfo info = targetType.GetConstructor(types);
object targetObject = info.Invoke(null);
if( targetObject == null )
throw new ArgumentException("Can't instantiate type " + typeName);

return targetObject;
}

private static Type ResolveType(string typeString)
{
int commaIndex = typeString.IndexOf(",");
string className = typeString.Substring(0, commaIndex).Trim();
string assemblyName = typeString.Substring(commaIndex + 1).Trim();

// Get the assembly containing the handler
Assembly assembly = null;
try
{
assembly = Assembly.Load(assemblyName);
}
catch
{
try
{
assembly = Assembly.LoadWithPartialName(assemblyName);
}
catch
{
throw new ArgumentException("Can't load assembly " + assemblyName);
}
}

// Get the handler
return assembly.GetType(className, false, false);
}
}


VB.Net

Public NotInheritable Class ObjectFactory

' Private constructor to prevent instantiation
Private Sub New()
End Sub

Public Shared Function Create(ByVal typeName As String) As Object

' resolve the type
Dim targetType As Type = ResolveType(typeName)
If targetType Is Nothing Then
Throw New ArgumentException("Can't load type " + typeName)
End If

' get the default constructor and instantiate
Dim types(-1) As Type
Dim info As ConstructorInfo = targetType.GetConstructor(types)
Dim targetObject As Object = info.Invoke(Nothing)
If targetObject Is Nothing Then
Throw New ArgumentException("Can't instantiate type " + typeName)
End If

Return targetObject
End Function

Private Shared Function ResolveType(ByVal typeString As String) As Type

Dim commaIndex As Int32 = typeString.IndexOf(",")
Dim className As String = typeString.Substring(0, commaIndex).Trim()
Dim assemblyName As String = typeString.Substring(commaIndex + 1).Trim()

' Get the assembly containing the handler
Dim targetAssembly As [Assembly] = Nothing
Try
targetAssembly = [Assembly].Load(assemblyName)
Catch
Try
targetAssembly = [Assembly].LoadWithPartialName(assemblyName)
Catch
Throw New ArgumentException("Can't load assembly " + assemblyName)
End Try
End Try

' Get the type
Return targetAssembly.GetType(className, False, False)
End Function

End Class

Performance

Reflection is slower than other methods of creating objects. While the class I outlined here provides a great deal of flexibility it is not suitable for high volume applications. There are tricks that can be used such as caching type info to boost the performance. This approach should be evaluated for suitability in any application. If performance is a key issue then a different architecture may be called for.
Take the demo application for a test drive. Hopefully this will open your eyes as to the true power of reflection.
Article Discussion: