【问题标题】:Why does field declaration with duplicated nested type in generic class results in huge source code increase?为什么泛型类中重复嵌套类型的字段声明会导致源代码大量增加?
【发布时间】:2012-12-20 01:50:55
【问题描述】:

场景非常少见,但非常简单:定义一个泛型类,然后创建一个从外部类继承的嵌套类,并在嵌套中定义一个关联字段(self 类型)。代码 sn-p 比描述简单:

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

反编译IL后,C#代码如下:

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 在这种情况下,类型在某些方面有所不同吗?

当事情变得非常棘手时

您可以查看以下课程的decompiled source code。它真的很大,总长度为 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

使用的工具

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

【问题讨论】:

标签: c# .net c#-4.0 generics nested


【解决方案1】:

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

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

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

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

接下来要注意的是在

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

有无数种方法可以引用嵌套类型B。一个是A.B,另一个是A.B.B,以此类推。语句typeof(A.B) == typeof(A.B.B) 返回true

当您将这两者结合起来时,按照您所做的方式,会发生一些有趣的事情。 Outer&lt;T&gt;.Inner 类型与 Outer&lt;T&gt;.Inner.Inner 类型不同。 Outer&lt;T&gt;.InnerOuter&lt;Outer&lt;T&gt;.Inner&gt; 的子类,而Outer&lt;T&gt;.Inner.InnerOuter&lt;Outer&lt;Outer&lt;T&gt;.Inner&gt;.Inner&gt; 的子类,我们之前确定它与Outer&lt;T&gt;.Inner 不同。所以Outer&lt;T&gt;.Inner.InnerOuter&lt;T&gt;.Inner 指的是不同的类型。

在生成 IL 时,编译器总是使用完全限定的类型名称。您已经巧妙地找到了一种方法来引用名称长度以指数速率增长的类型。这就是为什么当您增加 Outer 的通用数量或将额外级别 .Y 添加到 Inner 中的字段 field 时,输出 IL 大小和编译时间会增长得如此之快。

【讨论】:

  • 在我看来,我终于掌握了这个领域不同声明之间的差距。感谢您的帮助。
【解决方案2】:

这不是答案!

您的问题涉及很多方面。一个小问题是:如果一个类型包含(因为继承,否则不允许)一个与类型本身同名的嵌套类型,并且如果在类型内部使用了该名称,那么该名称指的是什么?

很难用语言表达,但这是我想到的例子:

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?
  }
}

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

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

【讨论】:

  • 你的分析是正确的; “is a kind of”关系比“is a member of my container”关系“更密切”。
【解决方案3】:

从使用当前类型参数化的泛型继承通常称为Curiously recurring template patternEric Lippert, previously on the C# compiler team 不鼓励这样做。

在您的情况下,嵌套类 Outer&lt;A&gt;.Inner 被定义为继承自 Outer&lt;Inner&gt;。这意味着嵌套类通过继承包含嵌套类的定义。这会产生嵌套类的无限定义:Outer&lt;A&gt;.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 类型是否在某些方面有所不同?

您更改了类 Inner 的类型定义,使其字段属于不同类型。 Outer&lt;A&gt;.Inner 的实例可能(我尚未验证)可转换为其他 Inner 类型,但它们是 2 种类型定义。

【讨论】:

  • that its field is of a different type 你能具体说明原因吗?很难理解为什么通过继承访问会以某种方式改变自身的静态类型
  • 你:"该字段被声明为类型Inner,在此范围内指的是当前定义的类型" 实际上,我认为这不正确。我可以提供另一个例子。我认为此范围内的Inner 对应于名为Innerinherited 成员。
  • 我在答案中添加了一个示例来显示类的扩展定义
  • 我提交了一个答案,实际上只是试图解释我上面第一条评论的意思。
  • 感谢您的回复,在玩了不同的配置后,我终于明白了这种情况。感谢帮助。如果您不介意,我会接受迈克的回答,因为他会更深入地扩展。觉得还是很难选择)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多