2016-12-25 2 views
4

Может кто-нибудь объяснить мне, почему приведенный ниже код выводит то, что он делает? Почему T - строка в первой, а не Int32, и почему в следующем выходе это противоположный случай?Наследование и общий тип настройки

Эта головоломка из interview with Eric Lippert

Когда я смотрю через код, я действительно понятия не имею, если он собирается быть Int32 или строка:

public class A<T> 
    { 
     public class B : A<int> 
     { 
      public void M() { System.Console.WriteLine(typeof(T)); } 
      public class C : B { } 
     } 
    } 

    public class P 
    { 
     public static void Main() 
     {    
      (new A<string>.B()).M(); //Outputs System.String 
      (new A<string>.B.C()).M(); //Outputs System.Int32 
      Console.Read(); 
     } 
    } 
+1

https://blogs.msdn.microsoft.com/ericlippert/2007/07/30/an-inheritance-puzzle-part-two/ –

ответ

2

слегка Изменение кода:

public class A<T> 
{ 
    public class B : A<int> 
    { 
     public void M() { System.Console.WriteLine(typeof(T)); } 
     public class C : A<T>.B { } 
    } 
} 

public class P 
{ 
    public static void Main() 
    {    
     (new A<string>.B.C()).M(); //Outputs System.String 
    } 
} 

Обратите внимание, как я изменил C «s базовый класс от B к A<T>.B. Это изменяет выход от System.Int32 до System.String.

Без этого A<string>.B.C получен не от A<string>.B, а от A<int>.B, вызывая поведение, которое вы видели.Это потому, что имена, определенные в базовых классах, доступны по неквалифицированному поиску, а имя B определено в базовом классе A<int>.

+0

Можно ли сказать, что класс может получить доступ к введенному значению T, если это не более чем один «уровень» от него? Например. (новый A .B()). M(); выводит String, поскольку к B обращается непосредственно после A , что означает, что значение T заменяет значение, которое B наследует по умолчанию? Принимая во внимание, что (новый A .B.C()). M(); выводит Int32, потому что один раз в C, теперь он находится на 2 уровнях от введенного значения T String и поэтому использует унаследованное значение по умолчанию Int32. –

+0

@Backwards_Dave Нет, это не так, и определение 'M' в' C' показало бы, что это неверно. 'A .B.M' собирается печатать' T', независимо от того, сколько уровней находится от него, когда вы его вызываете. Сложная часть состоит только в том, что 'A .B.M' получает вызов, а не' A .B.M', из-за вводящего в заблуждение базового класса. – hvd

+0

Позвольте мне попытаться объяснить это снова. Если T течет вниз через наследование, оно отменяет любые значения T по умолчанию, определенные в наследовании. Однако, как только происходит поток «вверх», в этом случае, когда C вызывает M(), который определен в родительском классе C, то указанное значение T теряется и используется значение T по умолчанию для Int32. Если M() была объявлена ​​на C, а не B, то не было бы восходящего потока. Не совсем уверен, какие слова использовать здесь, может быть, «поток» не очень хорош, но я надеюсь, что вы знаете, что я имею в виду. –

4

Метод M внутри B печатает typeof(T)A<T>, A является родительским классом B.

Так, независимо от того, B происходит от того, что, M отпечатков typeof(T) что является String.

So A<T>.B.M отпечатки ближайшего A's T.

Так A<string>.B.M напечатает string

Теперь разложим выражение A<string>.B.C, что эквивалентно A<string>.B.A<int>.BC является A<int>.B), поэтому метод A<string>.B.A<int>.B.M напечатает ближайший T.

A<string>.B.A<int>.B.M напечатает int

+0

Почему C равно A .B? Точнее, почему потерянное значение T, которое является String, теряется, и используется значение по умолчанию для Int? –

+0

Это не равно, это то, как типы замены типа разрешают типы. –

3

К Introduction to Generics T также доступна в гнездовой классе. Это случай с классом B, который вложен в A. С другой стороны, C вложен в B, а T B доступен в C. Как вы можете видеть, T B - это int, а метод, вызванный на C, будет использовать int в качестве общего параметра.

3

Метод M() Всегда печатает тип родового параметра родительского класса в своем классе:

Так (new A<string>.B.C()).M(); должен печатать общий тип B который всегда int. (Вы можете увидеть B всегда A<int>)

(new A<string>.B()).M(); Также необходимо распечатать string потому родительским B является A<string>.

+0

Я не уверен, что я не видел выход. Я мог правильно догадаться о выходе на первый взгляд, но после просмотра результата не так сложно определить, как печатается результат. Чу головоломка легко разрешить патруль;) –

3

Может кто-нибудь объяснить мне, почему приведенный ниже код выводит то, что он делает?

Суть дела заключается в определении значения B в class C : B. Рассмотрим версию без дженериков: (для краткости я буду опускать общественность.)

class D { class E {} } 
class J { 
    class E {} 
    class K : D { 
    E e; // Fully qualify this type 
    } 
} 

Это может быть J.E или D.E; что он? Правило в C# при разрешении имени - это поиск иерархии базового класса, и только если это не удается, посмотрите на свой контейнер. K уже имеет элемент E по наследованию, поэтому ему не нужно смотреть на его контейнер, чтобы обнаружить, что его контейнер имеет элемент E посредством сдерживания.

Но мы видим, что головоломка имеет такую ​​же структуру; это просто запутывается дженериками. Мы можем относиться к родовым как шаблон и просто выписывать конструкции A-оф-строки и A-of-ИНТАС классов:

class A_of_int 
{ 
    class B : A_of_int 
    { 
    void M() { Write("int"); } 
    class C : B { } // A_of_int.B 
    } 
} 
class A_of_string 
{ 
    class B : A_of_int 
    { 
    void M() { Write("string"); } 
    class C : B {} // still A_of_int.B 
    } 
} 

И теперь должно быть ясно, почему A_of_string.B.M() пишет string но A_of_string.B.C.M() пишет int ,