Generic Factory: New feature or misunderstanding?!

Damir Dobric Posts

Next talks:

 

    

Follow me on Twitter: #ddobric



 

 

Archives

 

.NET is a platform which integrates a huge number of features experienced in the last 20 years of software development. There are many developers, which find all this new and exciting. For example, object oriented development. An experienced VB-developer does not need much time to learn many of important object oriented rules and practices. It is very easy to start to develop in object oriented manner.

However, to start to feel and think this way, it is required much more time than many of us think.

In last 6 years of .NET development, there was lot of exiting examples, which used object oriented patterns just wrong way. One of them is described in this article.

 

Generics are for sure one of most important new features of .NET2.0. At this moment it is very important to know where generics come from and what they are. C++ developers know that they are a resurrection of templates, died in the time of .NET1.0.

 

Generics in C# are instantiated at the runtime and templates in C++ are instantiated during compile or link time. Additionally, C# generics are type safe and guarantee that all operations on type will succeed.

 

By using of generics I will analyze the factory pattern, which is often used the wrong way. Assume there is a very simple method, which creates the object of some specific type described by the input parameter like shown in this code snippet:

 

        public static object CreateInstance(string type)

        {

            if (type == "type1")

                return new Type1();

            else if (type == "type@")

                return new Type2();

            else

                throw new Exception("Bad type");

        }

 

This code is very simple and works fine. However thanks to generics there is also a new way to do the same thing as shown bellow:

 

  public static T CreateInstance<T>() where T:new()

  {

            T t = new T();

  }

 

  Even more there are also different ways to prove the type of ‘T’:

                T t = new T();

 

      if (t is int)

            ...;

      else if (typeof(T).IsAssignableFrom(typeof(long)))

            ...;

       else if (typeof(T).IsInstanceOfType(typeof(uint)))

            ...;

       else

            throw new Exception("Bad type");

 

All this seems to be modern and simply cool way to implement a factory. However it is mostly very bad pattern.

Why?

To understand what here happens, it is important to understand how generics works. Because generics are instantiated on the runtime, the used binding is late-binding (slow-binding). To prove this I disassembled the method CreateInstance<T>:

 

// Code size       38 (0x26)

  .maxstack  2

  .locals init ([0] !!T CS$1$0000,

           [1] !!T CS$0$0001)

  IL_0000:  nop

  IL_0001:  ldloca.s   CS$0$0001

  IL_0003:  initobj    !!T

  IL_0009:  ldloc.1

  IL_000a:  box        !!T

  IL_000f:  brfalse.s  IL_001c

  IL_0011:  ldloca.s   CS$0$0001

  IL_0013:  initobj    !!T

  IL_0019:  ldloc.1

  IL_001a:  br.s       IL_0021

  IL_001c:  call       !!0 [mscorlib]System.Activator::CreateInstance<!!0>()

  IL_0021:  stloc.0

  IL_0022:  br.s       IL_0024

  IL_0024:  ldloc.0

  IL_0025:  ret

 

 

 

It is obvious, that dynamic instantiating (late-binding) is used in the generic method.This kind of invocation is known as slow one. Now, let’s take a look on the usual factory pattern shown in the first example.

 

.locals init ([0] object CS$1$0000)

  IL_0000:  nop

  IL_0001:  newobj     instance void ConsoleApplication3.Type1::.ctor()

  IL_0006:  stloc.0

  IL_0007:  br.s       IL_0009

  IL_0009:  ldloc.0

  IL_000a:  ret

 

In this case the early-binding is used. Because the type is known at the compile time the compiler generates the exact type.

 

To prove the real performance of both methods I implemented following example. Methods t1 and t2 implement generic and none-generic factory. Methods c1 and c2 wraps up calls to t1 and t2 respectively, so I can be sure that required boxing in c2 is also calculated during performance measure.

 

 

       static void Main(string[] args)

       {

                   for (int n = 0; n < 3000; n++)

            {

               c1();

              c2();

            }

       }

 

 

        public static void c1()

        {

            p = t1<Program>();

        }

 

        public static void c2()

        {

            p = (Program)t2();

        }

 

        public static T t1<T>()

            where T : new()

        {

            return new T();

        }

 

        public static object t2()

        {

            return new Program();

        }

 

 Here are results of instrumentation:

 

Method  calls  exclusive   inclusive-time

------------------------------------------------------------

c1()   3000   0.934185      8.431655     

t1()   3000   0.332074      7.497470     

c2()   3000   0.444161      1.249673     

t2()   3000   0.375057      0.805512            

 

Conclusion :

 

If described pattern is required note that generics under the hub use late-binding by instantiating of the generic type. This is a reason why generics are slower in such cases than using new object() and corresponded cast.

 

However, there is a case where CreateInstance<T> can and should be used. That is when your implementation of the method requires late binding: Activator.CreateInstance().

 

 

Damir Dobric

www.daenet.de

 

 


Posted Mar 14 2006, 12:40 AM by Damir Dobric
Filed under:
developers.de is a .Net Community Blog powered by daenet GmbH.