I recently got a question on generic type identity. I will illustrate the question using this simple app:
class Base<T> { }
class Derived<T> : Base<T> { }
class App
{
static void Main()
{
Console.WriteLine(typeof(Base<>) == typeof(Derived<>).BaseType);
}
}
Can you guess what will be printed out?
Since the base type of Derived<T> is Base<T>, some might think this sample will print out true. However false was printed out. Why?
To answer this question we must first understand the difference between the formal type parameter and the actual type parameter. Formal type parameters are those that appear in definitions of generic types. They are templates that will be replaced when a constructed generic type is instantiated from the generic type definition. The types that replace them are the actual type parameters. Sometimes actual type parameters of one type can be the formal type parameters of another. In class Base<T>, T is the formal type parameter of type Base. In class Derived<T> : Base<T>, T is the formal parameter of Derived. It is also used to instantiated Base so it is the actual parameter for Base. The key here is that the T in Base<T> is different from the T in Derived<T>. They share the same name but are actully two different types.
Are you confused? It will be easier to understand if I had used different names for the formal parameters of Base and Derived:
class Base<T> { }
class Derived<U> : Base<U> { }
class App
{
static void Main()
{
Console.WriteLine(typeof(Base<>) == typeof(Derived<>).BaseType);
}
}
The formal type parameter of Base is still called T. But the formal type parameter of Derived is called U now which is also used to instantiated Base. So the base type of Derived<U> is Base<T> with the formal type parameter T replaced by an actual type U. No wonder it is not the same type as Base<T>.
If you ILDASM the first sample above and enable “show token values” you will see:
.class /*02000002*/ private auto ansi beforefieldinit Base`1<T>
extends [mscorlib/*23000001*/]System.Object/*01000001*/
{
} // end of class Base`1
.class /*02000003*/ private auto ansi beforefieldinit Derived`1<T>
extends class Base`1/*02000002*/<!T>/*1B000001*/
{
} // end of class Derived`1
The token for Base`1<T> is 02000002 which is a typedef. The token for the base type of Derived`1<U> is 1B000001 which is a typespec. We all know that typespec tokens represent arrays, byrefs, pointers, and instantiated generic types. In this case it is an instantiated generic type which is different from its generic type definition represented by the typedef token 02000002. In MetaInfo you will see:
TypeDef #1 (02000002)
-------------------------------------------------------
TypDefName: Base`1 (02000002)
Flags : [NotPublic] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit] (00100000)
Extends : 01000001 [TypeRef] System.Object
1 Generic Parameters
(0) GenericParamToken : (2a000001) Name : T flags: 00000000 Owner: 02000002
Method #1 (06000001)
-------------------------------------------------------
MethodName: .ctor (06000001)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886)
RVA : 0x00002050
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
... ... ... ...
TypeSpec #1 (1b000001)
-------------------------------------------------------
TypeSpec : GenericInst Class Base`1< Var!0>
MemberRef #1 (0a000005)
-------------------------------------------------------
Member: (0a000005) .ctor:
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Now can you guess the output of the following program?
class Base<T> { }
class Derived<U> : Base<U> { }
class App
{
static void Main()
{
Type t1 = typeof(Base<>);
Type t2 = typeof(Derived<>);
Type t3 = t2.BaseType;
Console.WriteLine(t1.IsGenericTypeDefinition);
Console.WriteLine(t2.IsGenericTypeDefinition);
Console.WriteLine(t3.IsGenericTypeDefinition);
Type p1 = t1.GetGenericArguments()[0];
Type p2 = t2.GetGenericArguments()[0];
Type p3 = t3.GetGenericArguments()[0];
Console.WriteLine(p1 == p2);
Console.WriteLine(p1 == p3);
Console.WriteLine(p2 == p3);
Type t4 = t1.MakeGenericType(p1);
Type t5 = t1.MakeGenericType(p2);
Type t6 = t1.MakeGenericType(p3);
Console.WriteLine(t4 == t1);
Console.WriteLine(t5 == t1);
Console.WriteLine(t6 == t1);
Console.WriteLine(t4 == t3);
Console.WriteLine(t5 == t3);
Console.WriteLine(t6 == t3);
}
}