Понимание вложенных родовых классов в С# с викториной

Во время разговора с коллегой о С# он показал мне код С#, который я должен был предсказать вывод. В первую очередь это выглядело просто, но это было не так. Я не могу понять, почему С# действует таким образом.

Код:

public class A<T1>
{
    public T1 a;

    public class B<T2> : A<T2>
    {
        public T1 b;

        public class C<T3> : B<T3>
        {
            public T1 c;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        A<int>.B<char>.C<bool> o = new A<int>.B<char>.C<bool>();

        Console.WriteLine(o.a.GetType());
        Console.WriteLine(o.b.GetType());
        Console.WriteLine(o.c.GetType());

        Console.ReadKey();
    }
}

Вывод:

System.Boolean
System.Char
System.Int32

Исправьте меня, если я ошибаюсь, но я понимаю, что o.a имеет тип bool, потому что C<T3> наследует от B<T3> и B<T2> наследует от A<T2>. И я также могу понять, что o.c имеет тип int, потому что тип c есть T1, который он получает из внешнего класса (я думаю).

Моя голова почти взрывается, когда я пытаюсь понять, почему o.b имеет тип char. Может кто-нибудь объяснить это мне?

Ответ 1

Это старая головоломка, и это довольно сложно. Когда я отдал его самому Андерсу, он не получил ответ в первый раз!

Я думаю, что версия, которую дал вам ваш коллега, из блога Cyrus:

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

В моем блоге немного более простая версия.

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

Решение моей версии находится здесь:

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

Вкратце, причина запутанного поведения заключается в том, что когда у вас есть имя, которое существует как во внешнем классе, так и в базовом классе, базовый класс "выигрывает". То есть, если у вас есть:

public class B
{
  public class X {}
} 
public class P
{
  public class X
  {
    public class D : B
    {
      public class N : X {}
    }
  }
}

Тогда P.X.D.N наследует от B.X, а не от P.X. Паззл делает вложенные общие типы таким образом, что одно и то же объявление может быть названо как с помощью "внешних", так и "базовых" путей поиска, но имеет разные значения в каждом из-за общей конструкции.

В любом случае, прочитайте объяснение в сообщениях в блоге, и если все еще не ясно, задайте более конкретный вопрос.

Ответ 2

Хорошо, мой первый ответ был неправильным. Вложение важно:

in o.b.GetType() b является членом окружающего класса, который создается как B<char>, который наследует от A<char>, который, в свою очередь, делает T1 равным char. Не совсем понятно следующее (ручное создание экземпляра для A_int.B_char.C_bool):

public class A_bool
{
    public bool a;

    public class B_bool : A_bool
    {
        public bool b;
    }
}

public class A_char
{
    public char a;

    public class B_bool : A_bool
    {
        public char b;
    }
}

public class A_int
{
    public int a;

    public class B_char : A_char
    {
        public int b;

        public class C_bool : A_char.B_bool
        {
            public int c;
        }
    }
}

Здесь C_bool можно было бы получить из A_bool.B_bool, верно? Но так как мы вложены в A_char, которые предпочитают.