Generic Class Factory – C# Part 3 / 4

This 3rd post in the generic class factory series focuses on creating objects in C# with an elegant class that uses the Type class and reflection’s Activator object.  The first post in the series provides an introduction to the generic class factory pattern, when it should be used, how it works, and shows a simple implementation in C++.  If you are not familiar with the generic class factory pattern, then please read this post first.  The second post is also in C++ and shows how a generic class factory can be designed to create unrelated types.

The following C# generic class factory uses the Type class.  The Type class is the root of System.Reflection and provides a way to access metadata.  Types can be passed to methods without using generics, stored in a container, and then retrieved at a later time to create an object.  There is no way to store a type in a container in unmanaged C++ and no way to create an object from a stored type.  What this means in C# is that the constructor for an object can be called directly by the CreateObj method – which saves code and time by not having to implement the static creation methods.  Here is the full class:

An Elegant C# Generic Class For Creating Unrelated Objects

public class GenericFactory<TKey>
{
   //
   private readonly Dictionary<TKey, Type> FactoryMap; // Used to map the key to the type of object.
   //
   public GenericFactory()
   {
      // Create the dicionary that will hold the key and type of object associated with that key.
      FactoryMap = new Dictionary<TKey, Type>();
   }

   public void Register(TKey key, Type thisType)
   {
      // This method registers a type so that it can be created later.
      // An exception is thrown if the type to be registered is either an interface or abstract.
      //
      if ((thisType.IsInterface) || (thisType.IsAbstract))
         throw new ArgumentException("GenericFactory::Register: type must not be an interface or abstract.");
      FactoryMap.Add(key, thisType);
   }

   public TType CreateObj<TType>(TKey key, params object[] args)
   {
      // This method creates an object based upon the specified key that was used to register the type earlier.
      // The Register method must have been called for this key, or an exception will be thrown.
      //
      Type ThisType;
      if (FactoryMap.TryGetValue(key, out ThisType))
         return (TType)Activator.CreateInstance(ThisType, args);
      // The key was not found.  Most likely the key was not properly registered.  So throw an exception here.
      throw new ArgumentException("GenericFactory::CreateObj: type has not been registered for this key.");
   }
}

The Register and CreateObj methods are used to manage objects of various types.  The C++ function pointer has been replaced by the Type class in C#, which is stored in the dictionary.  What this implies is that the static object creation methods that had to be written in C++ don’t have to be written in C# – since the constructors can be called directly by the class factory.  This is a real advantage that C# has over unmanaged C++.  To accomplish this little feat, the .NET Framework library offers an Activator class that can be used to create any type with any number of arguments, or even no arguments.  C++ does not offer anything like the Activator class.

In the CreateObj method, an array of objects replace the vector of void pointers defined in the C++ generic classes.  The params keyword is used so that a variable number of individual parameters can be specified or an array of objects used instead.  There must be a constructor defined that accepts the specified number of parameters.

The CreateObj method, which is defined as:

public TType CreateObj<TType>(TKey key, params object[] args)

calls the CreateInstance method on the Activator object:

return (TType)Activator.CreateInstance(ThisType, args);

The constructor for the type that gets called depends on the number of elements in args.  If args is null, then the default parameterless constructor is called.  If args has just one element, then the constructor with one parameter is called.  If args has two elements, then then the constructor with two parameters is called.  This provides quite a bit of flexibility to control the constructor that is called.  To see this in action, try debugging through the code and stepping into the CreateInstance method.  Three constructors are provided in the test classes, which are defined in the TestHelperClasses.cs (located in the download file), and the test code below tests each of them.

The following code tests the C# generic factory that uses the Type class to manage object creation and verifies that all the objects were created correctly:

// Create a generic factory that works with key strings.
GenericFactory<string> GenFact = new GenericFactory<string>();
// Register each type of object that can be created.
GenFact.Register("Red", typeof(RedColor));
GenFact.Register("Blue", typeof(BlueColor));
GenFact.Register("Pup", typeof(Dog));
GenFact.Register("Nag", typeof(Horse));
// Create a Red color object and save it into an object.
object ColorObj = GenFact.CreateObj<object>("Red");
Color Red1 = (Color)ColorObj;
if (Red1.Hue != 0)
   Console.WriteLine("TestTypeGenericFactory:GenFact.CreateObj() did not call the proper RedColor(0) constructor.");
// Create a Blue object and save it into a Color base class object.
Color Blue2 = GenFact.CreateObj<Color>("Blue", 2);
if (Blue2.Hue != 2)
   Console.WriteLine("TestTypeGenericFactory:GenFact.CreateObj() did not call the proper BlueColor(2) constructor.");
// Create a RedColor object.
RedColor Red3 = GenFact.CreateObj<RedColor>("Red", 3, "Name3");
if ((Red3.Hue != 3) && (Red3.Name != "Name3"))
   Console.WriteLine("TestTypeGenericFactory:GenFact.CreateObj() did not call the proper RedColor(3) constructor.");
// Using the same generic factory, create a completely different object that has nothing to do with colors.
object AnimalObj = GenFact.CreateObj<object>("Pup");
Animal Pup1 = (Animal)AnimalObj;
if (Pup1.YearsOld != 0)
   Console.WriteLine("TestTypeGenericFactory:GenFact.CreateObj() did not call the proper Dog(0) constructor.");
// Create a Horse object and save it into an Animal base class object.
Animal Horse2 = GenFact.CreateObj<Horse>("Nag", 2);
if (Horse2.YearsOld != 2)
   Console.WriteLine("TestTypeGenericFactory:GenFact.CreateObj() did not call the proper Horse(2) constructor.");
// Create another Dog object.
Dog Pup3 = GenFact.CreateObj<Dog>("Pup", 3, "Rex");
if ((Pup3.YearsOld != 3) && (Pup3.Name != "Rex"))
   Console.WriteLine("TestTypeGenericFactory:GenFact.CreateObj() did not call the proper Dog(3) constructor.");

Performance Concerns

When working with reflection, it is natural to be a little concerned about performance.  The Activator class provides some very nice functionality, but at what price performance wise?  The results of benchmark code that compares how fast an object can be created with the Activator class with how fast it can be created by using the new operator is summarized below (see Class Factory Benchmark Results.txt in the download file for full results).  The two argument constructor is called in both cases:

Activator.CreateInstance took 185.6ms to create 100,000 objects, or about .0019ms per object created.  Whereas using new took just 8ms to create 100,000 objects, which works out to be .00008ms per object created.  That corresponds to a 185.6 / 8 = 23.2 times longer to create an object with the Activator class compared to using the new operator.

Most of the time, 1/500th of a millisecond to construct an object should not be a major concern for most applications – especially if objects are created somewhat infrequently.  However, in the case where performance does matter, then an object pool could be used to store objects that are no longer being used.  One example of this is where objects are frequently created, used for a short time, and then discarded.  In this situation, an object pool could be used to hold the discarded objects.  If the discarded object pool is empty, than a new object is created.  Otherwise, an object would be retrieved and removed from the object pool.  When the object is no longer needed, it would be added to the object pool.  Another benefit to this approach is that the garbage collector would not need to work as hard.

An initialization method would need to be written that matches the constructor so that the object can be properly initialized with new parameter data.  It would be more modular to have the constructor call the initialization method in this case so that the object creation code can be centralized.  As object construction becomes more complex and time consuming, this idea can be extended by leaving parts of an object intact and only re-initializing the parts of an object that need specific reconstruction – which of course depends on the requirements of the application.

If greater object creation performance is desired, then one last C# generic class is provided, which is the subject of the last post in this series on generic class factories.

The code for this class may be downloaded by following this link.

Advertisements

About Bob Bryan

Software developer for over 20 years. Interested in efficient software methodology, user requirements, design, implementation, and testing. Experienced with C#, WPF, C++ , VB, Sql Server, stored procedures, and office tools. MCSD.
This entry was posted in C#, Patterns, software development and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s