41

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

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 有所不同?

当事情变得非常棘手时

你可以看到下面这个类的反编译源代码。它真的很大,总长度为 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

4

3 回答 3

27

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

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

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

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

接下来要注意的是,在

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.InnerOuter<T>.Inneris 的子类Outer<Outer<T>.Inner>whileOuter<T>.Inner.Inner是 的子类Outer<Outer<Outer<T>.Inner>.Inner>,我们之前将其确定为与 不同Outer<T>.Inner。所以Outer<T>.Inner.InnerOuter<T>.Inner指的是不同的类型。

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

于 2013-01-06T00:31:21.583 回答
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?
  }
}

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

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

于 2013-01-05T23:49:11.930 回答
6

从使用当前类型参数化的泛型继承通常被称为Curiously recurring 模板模式,并且以前在 C# 编译器团队的 Eric Lippert 不鼓励这种模式。

在您的情况下,嵌套类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 类型在某种程度上是否有所不同?

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

于 2013-01-05T23:15:11.860 回答