Подтвердить что ты не робот

Почему объявление поля с дублированным вложенным типом в родовом классе приводит к увеличению исходного кода?

Сценарий очень редок, но довольно прост: вы определяете общий класс, а затем создаете вложенный класс, который наследуется от внешнего класса и определяет ассоциативное поле (типа self) внутри вложенного. Фрагмент кода проще, чем описание:

class Outer<T>
{
    class Inner : Outer<Inner>
    {
        Inner field;
    }
}

после декомпиляции IL, код С# выглядит так:

internal class Outer<T>
{
    private class Inner : Outer<Outer<T>.Inner>
    {
        private Outer<Outer<T>.Inner>.Inner field;
    }
}

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

Inner.Inner field;

После декомпиляции это поле будет выглядеть так:

private Outer<Outer<Outer<T>.Inner>.Inner>.Inner field;

Я понимаю, что классная "натура" и наследование не совсем ладят друг с другом, но почему мы наблюдаем такое поведение? Является ли объявление типа Inner.Inner измененным типом вообще? Существуют ли в этом контексте типы Inner.Inner и Inner?

Когда все становится очень сложно

Вы можете увидеть декомпилированный исходный код для класса ниже. Он действительно огромный и имеет общую длину 12159 символов.

class X<A, B, C>
{
    class Y : X<Y, Y, Y>
    {
        Y.Y.Y.Y.Y.Y y;
    }
} 

Наконец, этот класс:

class X<A, B, C, D, E>
{
    class Y : X<Y, Y, Y, Y, Y>
    {
        Y.Y.Y.Y.Y.Y.Y.Y.Y y;
    }
}

приводит к сборке 27.9 MB (29,302,272 bytes) и Total build time: 00:43.619

Используемые инструменты

Компиляция выполняется под компиляторами С# 5 и С# 4. Декомпиляция выполняется dotPeek. Конфигурации сборки: Release и Debug

4b9b3361

Ответ 1

Ядро вашего вопроса, почему Inner.Inner - это другой тип, чем Inner. Как только вы это понимаете, ваши наблюдения за временем компиляции и сгенерированным размером IL-кода следуют легко.

Первое, что нужно отметить, это то, что когда у вас есть это объявление

public class X<T>
{
  public class Y { }
}

Существует бесконечное число типов, связанных с именем Y. Для каждого аргумента общего типа T существует один аргумент T, поэтому X<int>.Y отличается от X<object>.Y, и, что важно для более поздних версий, X<X<T>>.Y - это другой тип, чем X<T>.Y для всех T. Вы можете проверить это для разных типов T.

Следующее, что нужно отметить, это то, что в

public class A
{
  public class B : A { }
}

Существует множество способов обращения к вложенному типу B. Один из них - A.B, другой - A.B.B и т.д. Оператор typeof(A.B) == typeof(A.B.B) возвращает true.

Когда вы объединяете эти два, то, как вы это сделали, происходит что-то интересное. Тип Outer<T>.Inner не совпадает с типом Outer<T>.Inner.Inner. Outer<T>.Inner является подклассом Outer<Outer<T>.Inner>, а Outer<T>.Inner.Inner является подклассом Outer<Outer<Outer<T>.Inner>.Inner>, который мы установили ранее, поскольку он отличается от Outer<T>.Inner. Поэтому Outer<T>.Inner.Inner и Outer<T>.Inner относятся к разным типам.

При генерации IL, компилятор всегда использует полностью квалифицированные имена для типов. Вы умело нашли способ ссылаться на типы с именами, длина которых растет с экспоненциальными скоростями. Поэтому при увеличении общей арности Outer или добавлении дополнительных уровней .Y в поле field в Inner размер выходного IL и время компиляции растут так быстро.

Ответ 2

Это не ответ!

Есть много аспектов вашего вопроса. Немного: Если тип содержит (из-за наследования, не разрешенного иначе) вложенный тип с тем же именем, что и сам тип, и если это имя используется внутри типа, то какое имя имеет название?

Трудно выразить словами, но вот пример, который я имею в виду:

namespace N
{
  class Mammal
  {
    // contains nested type of an unfortunate name
    internal interface Giraffe
    {
    }
  }

  class Giraffe : Mammal
  {
    Giraffe g;  // what the fully qualified name of the type of g?
  }
}

Примечание: Это легко! Нет дженериков! Нет проблем с классом, наследующим свой собственный содержащий класс.

Вопрос в том, какой тип g? Это N.Giraffe (класс), или это N.Giraffe.Giraffe (интерфейс)? Правильный ответ - последний. Поскольку для определения значения для имени Giraffe сначала выполняется поиск членов текущего типа (в этом случае он находит интерфейс). Только если совпадение не найдено, вы переходите к членам текущего пространства имен (где будет найден текущий тип).

Ответ 3

Наследование из генератора, параметризованного текущим типом, часто называется Любопытно повторяющийся шаблон шаблона и обескуражен Эрик Липперт, ранее в команде компилятора С#.

В вашем случае вложенный класс Outer<A>.Inner определяется как наследующий от Outer<Inner>. Это означает, что вложенный класс содержит через наследование определение вложенного класса. Это дает бесконечное определение вложенных классов: Outer<A>.Inner.Inner.Inner...

Теперь, в вашем исходном определении

class Inner : Outer<Inner>
{
    Inner field;
}

поле объявляется как тип Inner, который в этой области относится к текущему типу. Когда вы изменили его на Inner.Inner, первый Inner ссылался на текущий тип, а второй .Inner ссылался на вложенный класс, полученный через наследование.

В качестве примера, позвольте расширить определение класса Inner, чтобы включить то, что исходит из наследования (и переименовать, чтобы сделать вещи немного яснее):

//original
class Outer<A>
{
    class Inner1 //: Outer<Inner1>
    {
        Inner1 field;

        //from inheritance
        class Inner2 : Outer<Inner1.Inner2>
        {
            Inner2 field;
        }
    }
}

//modified for Inner.Inner
class Outer<A>
{
    class Inner1 //: Outer<Inner1>
    {
        Inner1.Inner2 field;

        //from inheritance
        class Inner2 : Outer<Inner1.Inner2>
        {
            Inner2.Inner3 field;
        }
    }
}

Итак, вернитесь к своим вопросам:

Почему мы наблюдаем такое поведение? Является ли объявление типа Inner.Inner измененным? Существуют ли внутренние и внутренние типы Inner.Inner? В этом контексте?

Вы изменили определение типа класса Inner, чтобы его поле было другого типа. Экземпляр Outer<A>.Inner, возможно, (я не проверял) был бы применим к другому внутреннему типу, но они являются определениями типа 2.