typeof (T) in nested generic types

advertisements

I don't understand why the following behaves the way it does at all. I don't even know if it's caused by hiding or something else.

class A<T>
{

    public class B : A<int>
    {
        public void b()
        {
            Console.WriteLine(typeof(T).ToString());
        }
        public class C : B
        {
            public void c()
            {
                Console.WriteLine(typeof(T).ToString());
            }
        }
        public class D : A<T>.B
        {
            public void d()
            {
                Console.WriteLine(typeof(T).ToString());
            }
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        A<string>.B.C c = new A<string>.B.C();
        A<string>.B.D d = new A<string>.B.D();
        c.c();
        c.b();
        d.d();
        d.b();
    }
}

The questions are:

  1. Why does c.c() produce System.String while c.b() produces System.Int32?

  2. Why does d.d() and d.b() both produce System.String and not behave in exactly the same way as the class C?


This is a variation of a puzzle that I posted on my blog many years ago:

http://blogs.msdn.com/b/ericlippert/archive/2007/07/27/an-inheritance-puzzle-part-one.aspx

and Cyrus posted on his blog before that:

http://blogs.msdn.com/b/cyrusn/archive/2005/08/01/446431.aspx

See the discussion there for details.

Briefly: what does B mean in class C : B ? Check the container, class B. Does it contain any type called B? No. Then check the container's base class. The container's base class is A<int>. Does it contain anything called B? Yes. So this means class C : A<int>.B.

Now we say that c is A<string>.B.C. We call method A<string>.B.C.c() What is T throughout A<string>? Obviously string. So c.c() prints String for T.

Now we call A<string>.B.C.b() but there is no such method in A<string>.B.C directly. Where does it get this method? From its base class. What's it's base class? A<int>.B. So we call A<int>.B.b(). What is T throughout A<int>? Obviously int.

Now we come to A<string>.B.D.d(). The base class is irrelevant. T is string throughout A<string>.

And finally A<string>.B.D.b(). There is no such method on A<string>.B.D directly so it must get it from its base type. T is string throughout A<string>, so the base type is A<string>.B. Therefore this calls A<string>.B.b().

If that doesn't make sense to you, spell everything out. Let's substitute String for T:

class A_string
{
    public class B : A_int
    {
        public void b()
        {
            Console.WriteLine(typeof(string).ToString());
        }
        public class C : A_int.B // Note!
        {
            public void c()
            {
                Console.WriteLine(typeof(string).ToString());
            }
        }
        public class D : A_string.B
        {
            public void d()
            {
                Console.WriteLine(typeof(string).ToString());
            }
        }
    }
}

OK, that's one of the types. Now let's do the same for int:

class A_int
{
    public class B : A_int
    {
        public void b()
        {
            Console.WriteLine(typeof(int).ToString());
        }
        public class C : A_int.B // Note!
        {
            public void c()
            {
                Console.WriteLine(typeof(int).ToString());
            }
        }
        public class D : A_int.B
        {
            public void d()
            {
                Console.WriteLine(typeof(int).ToString());
            }
        }
    }
}

Now given those types it should be clear what A_string.B.C.c(), A_string.B.C.b(), etc, all print out.