Table of contents
There are several articles that describe how to write patterns in C#. However, the example implementations are usually simple in nature. Due to prior work with class factories, I became interested in the implementation of class factories in C#, but was not satisfied with the work I'd seen so far. This article provides five example implementations and compares their performance.
In my previous job as a Delphi programmer, I wrote an XSLT processor that could apply an XSLT style sheet to an XML document. One of the tasks was to implement a class factory that would instantiate the correct class based upon the XSLT element just read from the style sheet. For example, if the XSLT processor encountered an xsl:value-of
element in the style sheet then it would create an instance of class TXslValueOf
to represent the element.
In Delphi, this was a trivial task because classes are objects. It was a simple matter to store a reference to the class in a hash table, keyed on the name of the XSLT element. When the element name was encountered, the class could be retrieved from the hash table and its Create
method invoked.
I became interested in finding a similarly graceful solution in C#. A co-worker pointed me in the direction of System.Activator
. After playing with it a bit, I investigated two other implementations to see if better performance could be obtained. The first version of this article led to two other people, Wesner Moise and M. Veurman, suggesting modifications and alternative implementations.
The test program is a console program comprised of five parts: a Director
class, a Tester
class, a Globals
class to define static data, a number of classes representing XSLT elements, and several class factory implementations. The Director
class instantiates Tester
, tells Tester
to run the performance tests, and then reports the average time for each performance test. A portion of the Director
code is as follows:
[STAThread]
static void Main(string[] args)
{
Tester tester = new Tester(instances);
Console.WriteLine(String.Format("Running {0} passes of each test, " +
"creating {1} instances per test",
passes, instances));
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Running Hash-Switch test...");
int Total = 0;
for (int i = 0; i < passes; i++)
Total += tester.CreateViaHashSwitch();
Console.WriteLine("Hash-Switch: " + (Total/passes) + " ms");
Console.WriteLine(Environment.NewLine);
...
}
The Globals
class defines a static array of XSLT element names. This data is used by some of the class factory implementations.
public class Globals
{
public static string[] ElementNames = new string[24]
{
"xsl:apply-imports",
"xsl:apply-templates",
...
};
}
The classes representing XSLT elements don't do anything. They exist for the sole purpose of giving Tester
something to instantiate. The XSLT element classes all inherit from class BaseElement
and have names such as XslApplyTemplates
and XslForEach
.
The Tester
class implements several test methods. For example, CreateViaActivator
, CreateViaHashSwitch
, and CreateViaIfName
. Each test method instantiates the appropriate class factory and measures the time it takes the factory to create a specified number of objects.
Each class factory implementation resides in its own file (e.g., class ActivatorFactory
is in file ActivatorFactory.cs). This test program declares an abstract class factory with an abstract Create
method that is used to instantiate objects of the required type. Each concrete class factory implements the Create
method. The actual implementations are described in the following sections.
My first implementation of a class factory used the CreateInstance
method of System.Activator
. The implementation is in file ActivatorFactory.cs, class ActivatorFactory
.
The System.Activator.CreateInstance
method has a number of overloads and my first attempt used the overload expecting an assembly name and class name. However, I figured I was wasting time by repeatedly converting the name to a type. My final implementation uses a hash table to map the name of the XSLT element to a class type and then passes the class type to CreateInstance
. As shown in the following code snippet, the hash table is initialized in the ActivatorFactory
constructor:
public class ActivatorFactory : BaseClassFactory
{
private Hashtable _ElementHash;
public ActivatorFactory()
{
_ElementHash = new Hashtable(50, (float)0.5);
_ElementHash.Add(Globals.ElementNames[0], typeof(XslApplyImports));
...
}
...
Note that the Hashtable
is constructed with a load factor less than 1.0. As pointed out by Wesner Moise, this noticeably reduces the time it takes to locate a key. The implementation of the class factory's Create
method (i.e., the method that instantiates an object of the correct class) is as follows:
public override BaseElement Create(string name)
{
System.Type type = (System.Type) _ElementHash[name];
return (BaseElement) System.Activator.CreateInstance(type);
}
To me, using System.Activator
is elegant. If more classes are added, the only change that need be made is to update the constant array of class names and add an entry to a hash table in the factory's constructor. However, I had been warned this approach might be slow. To see if the warning was valid, I implemented other class factories (see the following sections). It turns out that using System.Activator.CreateInstance
can be several times slower than the alternatives presented in this article.
The next implementation of a class factory once again uses a hash table. Instead of mapping an element name to a class type, it maps the name to an integer index. A switch
statement then uses the index to instantiate the correct class. The implementation is in file HashSwitchFactory.cs, class HashSwitchFactory
.
The following code snippet shows part of the hash table's initialization:
public class HashSwitchFactory : BaseClassFactory
{
private Hashtable _ElementHash;
public HashSwitchFactory()
{
_ElementHash = new Hashtable(50, (float)0.5);
_ElementHash.Add(Globals.ElementNames[0], 0);
_ElementHash.Add(Globals.ElementNames[1], 1);
...
}
...
The following code snippet shows a portion of the switch
statement within the Create
method:
public override BaseElement Create(string name)
{
switch ((int) _ElementHash[name])
{
case 0:
return new XslApplyImports();
case 1:
return new XslApplyTemplates();
...
}
}
This approach to a class factory is several times faster than using System.Activator.CreateInstance
. However, it requires more source code to implement (I don't like big case statements) and comes across to me as less maintainable.
When I looked in the Rotor source code to see how Microsoft instantiates an XSLT class based upon an element name, I discovered they use a large if..else
statement. Each clause compares the element name to one of the names on record. If they match, the associated class is instantiated. For the sake of completeness, I added class IfNameFactory
in file IfNameFactory.cs to time an implementation using the same approach. The implementation of the factory is as follows:
public class IfNameFactory : BaseClassFactory
{
public override BaseElement Create(string name)
{
if (name == Globals.ElementNames[0])
{
return new XslApplyImports();
}
else if (name == Globals.ElementNames[1])
{
return new XslApplyTemplates();
}
else if (name == Globals.ElementNames[2])
...
}
}
As you can see, the code looks very similar to the case where a hash table and switch
statement was used. However, the performance of using an if
statement varies depending upon how many comparisons need to be made. The final version of the test program instantiates 24 different classes. With 24 classes, the if
statement is slower than the switch
statement. When the number of classes decreases (e.g., 10, 15), the if
statement is faster than the switch
statement. For this particular test program, the performance gain dropped off around 17 or 18 classes.
In this particular implementation, each class is instantiated the same number of times as every other class. If certain classes are instantiated more than other classes, the tests for those classes could be positioned earlier in the if
statement resulting in faster performance.
You can specify the number of classes to instantiate via the cutoff
constant in the Tester
class.
Shortly after the initial release of this article, Wesner Moise pointed out that a switch
statement works with strings. The implementation, defined as class SwitchNameFactory
in file SwitchNameFactory.cs, is as follows:
public class SwitchNameFactory : BaseClassFactory
{
public override BaseElement Create(string name)
{
switch (name)
{
case "xsl:apply-imports":
return new XslApplyImports();
case "xsl:apply-templates":
return new XslApplyTemplates();
...
}
}
Like the IfNameFactory
, it does not need a hash table. All of the logic and maintenance is contained within the switch
statement. When it comes to performance, it can be slower than the HashSwitchFactory
but faster than the IfNameFactory
as the number of classes increase.
M. Veurman presented another implementation with the focus on being object-oriented. The implementation, contained in unit ElementFactory.cs, defines an interface representing a class factory that instantiates an XSLT element. It then defines a concrete class factory for each XSLT element. The interface and sample class are shown in the following code snippet:
public interface IXslElementFactory
{
string ElementName
{
get;
}
BaseElement Create();
}
public class XslApplyImportsFactory : IXslElementFactory
{
public string ElementName
{
get { return "xsl:apply-imports"; }
}
public BaseElement Create()
{
return new XslApplyImports();
}
}
...
The class factory, named ElementFactory
, registers each element factory in its constructor. The registration maps the element factory's element name to an instance of the element factory.
public class ElementFactory : BaseClassFactory
{
Hashtable _ElementHash;
public ElementFactory()
{
_ElementHash = new Hashtable(50, (float)0.5);
RegisterElementFactory(new XslApplyImportsFactory());
RegisterElementFactory(new XslApplyTemplatesFactory());
...
}
...
private void RegisterElementFactory(IXslElementFactory factory)
{
_ElementHash.Add(factory.ElementName, factory);
}
The ElementFactory.Create
method retrieves the element factory from the hash table and calls the element factory's Create
method.
public override BaseElement Create(string name)
{
IXslElementFactory factory = (IXslElementFactory) _ElementHash[name];
return factory.Create();
}
This implementation is certainly object-oriented. Performance-wise, it fairs very well. When measured with this test program, it comes in at 2nd place when 18 or more classes are being produced by the class factory. However, being object-oriented has a cost in this situation. There are more lines of code required for each class supported by the class factory. This may be good or bad depending upon the person and project.
The release version of the test program was executed on a computer with 2.4 GHz Pentium 4 CPU and 512 MB of RAM. Each test was executed 10 times, with each pass instantiating one million objects. The times reported are the average of the 10 executions. The factory columns are ordered from fastest to slowest for the instantiation of 24 classes.
Classes instantiated |
Hash-Switch |
Element Factory |
Switch-Name |
If-Name |
System.Activator |
5 |
273 ms |
279 ms |
296 ms |
98 ms |
1303 ms |
10 |
267 ms |
275 ms |
287 ms |
171 ms |
1298 ms |
15 |
264 ms |
275 ms |
286 ms |
237 ms |
1303 ms |
18 |
265 ms |
276 ms |
287 ms |
281 ms |
1300 ms |
20 |
268 ms |
282 ms |
290 ms |
312 ms |
1300 ms |
24 |
273 ms |
289 ms |
293 ms |
362 ms |
1309 ms |
Each type of implementation for a class factory has different degrees of performance and flexibility. This test program had to create one million instances of an object in order to show noticeable differences in performance. Will many real life projects need to create that many objects in a short amount of time? Probably not. So for most situations, maintenance may be a higher priority than performance when deciding which implementation to use.
When it comes to maintenance, the implementations could be ranked as follows, from least maintenance required to most maintenance required. Note that the actual difference between the first two sets of factories is just a couple lines of code.
SwitchNameFactory
, IfNameFactory
HashSwitchFactory
, ActivatorFactory
ElementFactory
If being object-oriented is a priority, the ElementFactory
is the most object-oriented of the bunch and has very good performance.
If a class factory is to be used in situations where small numbers of objects are created and the time spent creating those objects is not important, using the ActivatorFactory
can improve the maintainability and elegance of the code.
If the class factory will be creating large numbers of objects and there are a relatively small number of classes that may be instantiated (e.g., 20 or less), the best performance is achieved using the IfNameFactory
. Performance may be improved by testing for frequently instantiated classes near the beginning of the if
statement.
If the class factory will be creating large numbers of objects and there are twenty or more classes that may be instantiated, the best performance could be achieved by using either the HashSwitch
, ElementFactory
, or SwitchNameFactory
.
- Jan 7th, 2003 - Initial release
- Jan 17th, 2003 - Refactored the code to show actual class factory implementations. Added Element Factory and Switch on Name implementations.