Sunday, March 12, 2006

Ian Horwill commented in my previous blog:
“Hi Venkat, enjoyed the show. Was intrigued by your examination of type info for generic types and I
think I found a bug - see my blog post for today
. Also I am not sure I understand what you mean
towards the end when you talk about constraining a generic type to a class or its superclass.
How would this be useful? You would not be able to use any subclass-specific behaviour without resorting
to type checking (similar to covariance not allowed for parameters of overriding methods).”

The little quirk that he stepped on is very interesting and is a bit deeper, actually. He has a class
Derived<T> which inherits from Base<T>. He then has code to display details of these classes.

What you would expect from this example is the following relationship:

However, what it actually is the following:


The base of Derived<T> (which is a generic type definition is not a generic type definition!
Here is a code that examines this:

namespace TestIt2
{
   public class Base<T> { }

   public class Derived<T> : Base<T> { }

   public class Program
   {
      public static void Main()
      {
         Type theType = typeof(Derived<int>);
         Info(theType);

         Type baseType = theType.BaseType;
         Info(baseType);

         Type genericTypeDef = theType.GetGenericTypeDefinition();
         Info(genericTypeDef);

         Type baseOfGenericTypeDef = genericTypeDef.BaseType;
         Info(baseOfGenericTypeDef);

         Type genericTypeDefOfBaseOfGenericTypeDef =
               baseOfGenericTypeDef.GetGenericTypeDefinition();
         Info(genericTypeDefOfBaseOfGenericTypeDef);

         Type baseOfGenericTypeDefOfBaseOfGenericTypeDef = 
         genericTypeDefOfBaseOfGenericTypeDef.BaseType;

         Info(baseOfGenericTypeDefOfBaseOfGenericTypeDef);
      }

      private static void Info(Type type)
      {
         Console.WriteLine("---------------");
         Console.WriteLine("Name={0}", type.Name);
         Console.WriteLine("Full Name={0}", type.FullName);
         Console.WriteLine("HashCode={0}", type.GetHashCode());
         Console.WriteLine("IsGenericType={0}", type.IsGenericType);
         Console.WriteLine("IsGenericTypeDefinition={0}"
         type.IsGenericTypeDefinition);
         Console.WriteLine("Generic Parameters:");
         foreach (Type arg in type.GetGenericArguments())
         {
            Console.WriteLine("\t" + arg);
         }
      }
   }
}

The output from above code is:

---------------
Name=Derived`1
Full Name=TestIt2.Derived`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=ne
utral, PublicKeyToken=b77a5c561934e089]]
HashCode=10564272
IsGenericType=True
IsGenericTypeDefinition=False
Generic Parameters:
System.Int32
---------------
Name=Base`1
Full Name=TestIt2.Base`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutr
al, PublicKeyToken=b77a5c561934e089]]
HashCode=10564152
IsGenericType=True
IsGenericTypeDefinition=False
Generic Parameters:
System.Int32
---------------
Name=Derived`1
Full Name=TestIt2.Derived`1
HashCode=10564024
IsGenericType=True
IsGenericTypeDefinition=True
Generic Parameters:
T
---------------
Name=Base`1
Full Name=
HashCode=10563904
IsGenericType=True
IsGenericTypeDefinition=False
Generic Parameters:
T
---------------
Name=Base`1
Full Name=TestIt2.Base`1
HashCode=10563784
IsGenericType=True
IsGenericTypeDefinition=True
Generic Parameters:
T
---------------
Name=Object
Full Name=System.Object
HashCode=2031066136
IsGenericType=False
IsGenericTypeDefinition=False
Generic Parameters:

As you can see the hash code for Base'1 in the section where "Full Name = " is blank is not the same as
the hash code in the following section. Also notice that the IsGenericTypeDefinition is false in the section
of interest.

I hope someone from Microsoft can shed some light on this.

I will answer his second part in the next blog.