简体   繁体   中英

typeof(T) within generic nested types

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 cc() produce System.String while cb() produces System.Int32 ?

  2. Why does dd() and db() 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>.BC . We call method A<string>.BCc() What is T throughout A<string> ? Obviously string . So cc() prints String for T .

Now we call A<string>.BCb() but there is no such method in A<string>.BC 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>.Bb() . What is T throughout A<int> ? Obviously int .

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

And finally A<string>.BDb() . There is no such method on A<string>.BD 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>.Bb() .

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.BCc() , A_string.BCb() , etc, all print out.

A<string>.BC 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM