为什么在泛型类中使用重复嵌套类型的字段声明会导致巨大的源代码增加?

Ily*_*nov 39 .net c# generics nested c#-4.0

场景非常罕见,但非常简单:定义一个泛型类,然后创建一个嵌套类,它继承自外部类并在嵌套中定义一个关联字段(self类型).代码片段比描述更简单:

class Outer<T>
{
    class Inner : Outer<Inner>
    {
        Inner field;
    }
}
Run Code Online (Sandbox Code Playgroud)

在反编译IL之后,C#代码如下所示:

internal class Outer<T>
{
    private class Inner : Outer<Outer<T>.Inner>
    {
        private Outer<Outer<T>.Inner>.Inner field;
    }
}
Run Code Online (Sandbox Code Playgroud)

这看起来很公平,但是当你改变字段的类型声明时,事情变得更加棘手.所以当我将字段声明更改为

Inner.Inner field;
Run Code Online (Sandbox Code Playgroud)

反编译后,此字段将如下所示:

private Outer<Outer<Outer<T>.Inner>.Inner>.Inner field;
Run Code Online (Sandbox Code Playgroud)

我明白,这个类'嵌套'和继承彼此并不相处,但为什么我们会观察到这种行为呢?在 Inner.Inner 类型声明已经发生任何变化的类型?是 Inner.Inner Inner 类型,在此情况下某种方式有什么不同?

当事情变得非常棘手

您可以在下面看到该类的反编译源代码.它非常庞大,总长度为12159个符号.

class X<A, B, C>
{
    class Y : X<Y, Y, Y>
    {
        Y.Y.Y.Y.Y.Y y;
    }
} 
Run Code Online (Sandbox Code Playgroud)

最后,这堂课:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

导致27.9 MB (29,302,272 bytes)装配和Total build time: 00:43.619

使用的工具

编译在C#5和C#4编译器下完成.反编译由dotPeek完成.构建配置:ReleaseDebug

Mik*_*ray 25

你的问题的核心是为什么Inner.Inner不同的类型Inner.一旦理解了这一点,您对编译时间和生成的IL代码大小的观察就会很容易.

首先要注意的是,当你有这个声明时

public class X<T>
{
  public class Y { }
}
Run Code Online (Sandbox Code Playgroud)

与名称相关的无限多种类型Y.每个泛型类型参数都有一个T,因此X<int>.Y不同于X<object>.Y以后的重要X<X<T>>.Y类型,而不是X<T>.Y所有类型T.您可以针对各种类型进行测试T.

接下来要注意的是

public class A
{
  public class B : A { }
}
Run Code Online (Sandbox Code Playgroud)

有很多种方法可以引用嵌套类型B.一个是A.B,另一个是A.B.B,依此类推.声明typeof(A.B) == typeof(A.B.B)返回true.

当你将这两者结合起来时,就会发生一些有趣的事情.类型Outer<T>.Inner与类型不同Outer<T>.Inner.Inner.Outer<T>.InnerOuter<Outer<T>.Inner>while Outer<T>.Inner.Inner的子类Outer<Outer<Outer<T>.Inner>.Inner>,它是我们之前建立的不同的子类Outer<T>.Inner.所以Outer<T>.Inner.Inner,Outer<T>.Inner指的是不同的类型.

生成IL时,编译器始终使用类型的完全限定名称.您巧妙地找到了一种方法来引用名称长度以指数速率增长的类型.这就是为什么当您在输出IL大小中Outer增加.Y字段的通用性或向字段添加其他级别时,编译时间会快速增长.fieldInner


Jep*_*sen 7

这不是答案!

你的问题有很多方面.一个小问题是:如果一个类型包含(因为继承,否则不允许)一个与类型本身同名的嵌套类型,如果在该类型中使用该名称,该名称引用什么?

用文字表达很难,但这是我心中的例子:

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

  class Giraffe : Mammal
  {
    Giraffe g;  // what's the fully qualified name of the type of g?
  }
}
Run Code Online (Sandbox Code Playgroud)

注意:这很简单!没有泛型!继承自己的包含类的类没有问题.

这里的问题是,什么类型的g?它N.Giraffe(一个类),还是它N.Giraffe.Giraffe(一个接口)?正确答案是后者.因为要找到名称的含义Giraffe,首先要搜索当前类型的成员(在这种情况下,查找接口).只有在那里找不到匹配时,才会进入当前命名空间的成员(将找到当前类型).


Mar*_*lin 6

从使用当前类型参数化的泛型继承通常称为奇怪的重复模板模式,并且之前在C#编译器团队中的Eric Lippert不鼓励这种模式.

在您的情况下,嵌套类Outer<A>.Inner被定义为继承自Outer<Inner>.这意味着嵌套类通过继承包含嵌套类的定义.这产生了嵌套类的无限定义:Outer<A>.Inner.Inner.Inner...

现在,在您的原始定义中

class Inner : Outer<Inner>
{
    Inner field;
}
Run Code Online (Sandbox Code Playgroud)

该字段被声明为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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

回到你的问题:

为什么我们观察到这种行为?Inner.Inner类型声明是否已更改类型?在这种情况下,Inner.Inner和Inner类型在某些方面有所不同吗?

您更改了类Inner的类型定义,以使其字段具有不同的类型.Outer<A>.Inner可能(我尚未验证)的实例可以转换为其他内部类型,但它们是2种类型定义.