Generic Class Factory – C# Part 4 / 4

This is the last post in the generic class factory series.  The previous posts included:

  • Generic factory introduction.  Please read this post first to understand why a class factory should be used instead of simply creating an object and how a generic class factory works.   This introductory post introduces a C++ generic class factory that only works to create objects of the same base class.
  • C++ generic factory that can be used to create objects of any type.  Most, if not all generic factory examples previously published only work to create objects of a specific base class or concrete type.  This post shows how to design a class factory that can be used to create any type of object – even those with different base types.  I have not seen this technique published before.
  • Elegant C# generic factory.  This post shows how to use the Type class and the reflection Activator object to link up to the proper constructor without having to define static creation methods required by the C++ generic factory classes.  The drawback of this technique is that it is significantly slower than creating an object with the new keyword.

In the elegant C# generic factory post, a benchmark was provided that shows the generic class factory is over 23 times slower than creating an object with the new keyword.  It was suggested that this is unlikely to significantly affect overall performance if objects are infrequently created.  It was also pointed out that objects that are frequently created and discarded, that performance could be improved by using an object pool for discarded objects and then simply retrieving, removing the object from the pool, and reinitializing the object as needed.  This should also help to minimize the impact of the garbage collector.

If an object pool can’t be used for some reason, and high performance of creation of objects from a generic factory is critical, then this post should help shed some light on how to achieve this objective.

This C# class uses the same model as used in the C++ generic classes, but uses a delegate instead of a function pointer in order to avoid the performance issues with reflection.  Delegates are somewhat similar to a C++ function pointer, but are type safe and can be used as callback methods.  Here is the full class:

A C# Generic Class Designed For Performance

public class GenericFactoryDelg<TKey, TType>
{
   // This class can be used to create an object of any type.
   //
   public delegate TType CreateObjectDelegate(params object[] args);

   private readonly Dictionary<TKey, CreateObjectDelegate> FactoryMap; // Used to map the key to the delegate static creation method used to create the object.

   public GenericFactoryDelg()
   {
      FactoryMap = new Dictionary<TKey, CreateObjectDelegate>();
   }

   public void Register(TKey key, CreateObjectDelegate ctor)
   {
      // This method registers a delegate to the method used to create the object so that it can be created later.
      //
      FactoryMap.Add(key, ctor);
   }

   public DType CreateObj<DType>(TKey id, params object[] args) where DType : TType
   {
      // 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.
      //
      CreateObjectDelegate Constructor = null;
      // Get the delegate to the constructor used to create the object, and then call it.
      if (FactoryMap.TryGetValue(id, out Constructor))
         return (DType)Constructor(args);
      throw new ArgumentException("No type registered for this id.");
   }
}

The delegate CreateObjectDelegate is defined as:

public delegate TType CreateObjectDelegate(params object[] args);

Like the C++ code, TType is a generic class parameter that identifies the type of the class to be created and the consumer of this class is responsible for defining a static creation method to create the object.  The FactoryMap dictionary object is used in the Register method to keep track of the delegate that is called in CreateObj.  In CreateObj, the generic constraint “where DType : TType” is required for the code to compile.  If the constraint is removed, then the compiler error “Cannot convert type ‘TType’ to ‘DType'” is produced.

This generic class factory can be used to create objects of unrelated types if object is used for the TTYPE class parameter.  For example:

GenericFactoryDelg<int, object> GFD = new GenericFactoryDelg<int, object>();

A delegate to the method used to create an object must be defined to use the CreateObjectDelegate defined in GenericFactoryDelg.  For example:

GenericFactoryDelg<int, object>.CreateObjectDelegate CreateRedColor = new GenericFactoryDelg<int, object>.CreateObjectDelegate(RedColor.CreateObj);

The Register method must be called to associate the creation delegate with the key.  For example:

GFD.Register(1, CreateRedColor);

The object can be created by calling the CreateObj method with the appropriate key.  For example:

Color Red3 = GFF.CreateObj<Color>(1, 3, "Name3");

The following code tests the GenericFactoryDelg class and verifies that the objects were successfully created:

// Create a generic factory that works with int keys.
GenericFactoryDelg<int, object> GFD = new GenericFactoryDelg<int, object>();
// Create the delegates used to create the objects.
GenericFactoryDelg<int, object>.CreateObjectDelegate CreateRedColor = new GenericFactoryDelg<int, object>.CreateObjectDelegate(RedColor.CreateObj);
GenericFactoryDelg<int, object>.CreateObjectDelegate CreateBlueColor = new GenericFactoryDelg<int, object>.CreateObjectDelegate(BlueColor.CreateObj);
GenericFactoryDelg<int, object>.CreateObjectDelegate CreateDogAnimal = new GenericFactoryDelg<int, object>.CreateObjectDelegate(Dog.CreateObj);
GenericFactoryDelg<int, object>.CreateObjectDelegate CreateHorseAnimal = new GenericFactoryDelg<int, object>.CreateObjectDelegate(Horse.CreateObj);
// Register each type that can be created with the generic class factory.
GFD.Register(1, CreateRedColor);
GFD.Register(2, CreateBlueColor);
GFD.Register(3, CreateDogAnimal);
GFD.Register(4, CreateHorseAnimal);
//
// Create a Red color object into an object and convert it to a Color object.
object ColorObj = GFD.CreateObj<object>(1);
Color Red1 = (Color)ColorObj;
if (Red1.Hue != 0)
   Console.WriteLine("TestDelegateGenericFactory:GF2.CreateObj() did not call the proper RedColor(0) constructor.");
// Create a Blue object and save it into a Color base class object.
Color Blue2 = GFD.CreateObj<Color>(2, 2);
if (Blue2.Hue != 2)
   Console.WriteLine("TestDelegateGenericFactory:GF2.CreateObj() did not call the proper BlueColor(2) constructor.");
// Create a Red object.
RedColor Red3 = GFD.CreateObj<RedColor>(1, 3, "Name3");
if (Red3.Hue != 3)
   Console.WriteLine("TestDelegateGenericFactory:GF2.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 = GFD.CreateObj<object>(3);
Animal Pup1 = (Animal)AnimalObj;
if (Pup1.YearsOld != 0)
   Console.WriteLine("TestDelegateGenericFactory:GF2.CreateObj() did not call the proper Dog(0) constructor.");
// Create a Horse object and save it into an Animal base class object.
Animal Horse2 = GFD.CreateObj<Animal>(4, 2);
if (Horse2.YearsOld != 2)
   Console.WriteLine("TestDelegateGenericFactory:GF2.CreateObj() did not call the proper Horse(2) constructor.");
// Create another dog object.
Dog Pup2 = GFD.CreateObj<Dog>(3, 4, "Rex");
if ((Pup2.YearsOld != 4) && (Pup2.Name != "Rex"))
   Console.WriteLine("TestDelegateGenericFactory:GF2.CreateObj() did not call the proper Dog(4) constructor.");

The performance of this generic class factory is much improved over the one that uses the Activator class; it is over six times faster.  See Class Factory Benchmark Results.txt in the download file for more information.

Series Conclusion

These series of posts have shown how a generic C++ class factory can be implemented to work with unrelated types.  An elegant C# generic class factory has been presented.  The benefit of this class is that it directly calls a class’s constructor and does not require static creation methods like the C++ generic class factory – which should save some code and time.  The drawback to this elegant class is that it is a little slow since it uses reflection to hook up to the proper constructor.  Finally, A C# generic class factory was also presented that is over 6 times faster.  The drawback to this class is that static creation methods must be implemented like the C++ generic class factory.

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#, 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