Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

A<string>.B.C inherits A<int>.B, because B in the base class declaration comes from the inside parent scope first. (to clarify, its parent scope is A<T>.B, which contains a type named B referring to A<int>.B, inherited from its base class A<int>)

Calling b() comes from its base class, in which T (from the parent scope) is int.

D explicitly inherits A<T>.B, using T from the outermost scope (A<T>), so its T always comes from A<> in its typename.

This is a very slightly more complex example of a puzzle Eric Lippert describes in this blog article, which is then explained in this article.

The short summary (I highly suggest just reading the article) is that in this case there is an ambiguity in the line C : B. A decision needs to be made as to what B actually means in this context; whether it's A<T>.B or A<int>.B. Essentially, the compiler chooses the latter, given a particular criteria of "betterness" described in the spec.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top