Typeof (T) в общих типах вложенных типов

Я не понимаю, почему следующее поведение ведет себя так, как оно есть. Я даже не знаю, вызвано ли это скрытием или чем-то еще.

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();
    }
}

Вопросы:

  • Почему c.c() создает System.String, а c.b() создает System.Int32?

  • Почему d.d() и d.b() создают System.String и не ведут себя точно так же, как класс C?

Ответы

Ответ 1

Это вариация головоломки, которую я опубликовал в своем блоге много лет назад:

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

и Сайрус опубликовали в своем блоге до этого:

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

См. обсуждение там для деталей.

Вкратце: что означает B в class C : B? Проверьте контейнер, class B. Он содержит любой тип, называемый B? Нет. Затем проверьте базовый класс контейнера. Базовый класс контейнера A<int>. Он содержит что-либо называемое B? Да. Таким образом, это означает class C : A<int>.B.

Теперь мы говорим, что c есть A<string>.B.C. Мы называем метод A<string>.B.C.c() Что такое T на всем протяжении A<string>? Очевидно, string. Поэтому c.c() печатает string для T.

Теперь мы называем A<string>.B.C.b(), но такого метода в A<string>.B.C нет. Где он получает этот метод? Из его базового класса. Что это за базовый класс? A<int>.B. Поэтому мы называем A<int>.B.b(). Что такое T на протяжении всего A<int>? Очевидно, int.

Теперь мы переходим к A<string>.B.D.d(). Базовый класс не имеет значения. T string на протяжении A<string>.

И наконец A<string>.B.D.b(). На A<string>.B.D такого метода нет, поэтому он должен получить его из своего базового типа. T string на протяжении A<string>, поэтому базовый тип A<string>.B. Поэтому это вызывает A<string>.B.b().

Если это не имеет смысла для вас, заклинайте все. Пусть подстановка String для 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());
            }
        }
    }
}

ОК, это один из типов. Теперь сделаем то же самое для 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());
            }
        }
    }
}

Теперь, учитывая эти типы, должно быть ясно, какие A_string.B.C.c(), A_string.B.C.b() и т.д. все распечатываются.

Ответ 2

A<string>.B.C наследует A<int>.B, потому что B в объявлении базового класса сначала появляется из внутренней родительской области. (чтобы уточнить, его родительская область A<T>.B, которая содержит тип с именем B, относящийся к A<int>.B, унаследованный от его базового класса A<int>)

Вызов b() происходит от его базового класса, в котором T (из родительской области) int.

D явно наследует A<T>.B, используя T из внешней области (A<T>), поэтому ее T всегда имеет значение A<> в его имени.

Ответ 3

Это очень немного более сложный пример головоломки Эрик Липперт описывает в эту статью в блоге, которая затем объясняется в в этой статье.

Краткий обзор (я настоятельно рекомендую просто прочитать статью) состоит в том, что в этом случае в строке C : B имеется двусмысленность. Необходимо принять решение относительно того, что в действительности означает B; будь то A<T>.B или A<int>.B. По сути, компилятор выбирает последний, учитывая особый критерий "блеска", описанного в спецификации.