【问题标题】:Why cannot C# generics derive from one of the generic type parameters like they can in C++ templates? [duplicate]为什么 C# 泛型不能像在 C++ 模板中那样从泛型类型参数之一派生? [复制]
【发布时间】:2010-12-22 23:50:51
【问题描述】:

为什么 C# 泛型不能像在 C++ 模板中那样从泛型类型参数之一派生?我的意思是我知道这是不可能的,因为 CLR 不支持,但是为什么呢?

我知道 C++ 模板和 C# 泛型之间的巨大差异 - 前者是编译时实体,必须在编译期间解决,而后者是一流的运行时实体。

不过,我没有看到原因为什么 CLR 设计者没有提出最终使 CLR 泛型类型能够从其泛型类型参数之一派生的方案。毕竟,这将是一个非常有用的功能,我个人非常想念它。

编辑:

我想知道一个核心问题,解决这个问题会在实施此功能时付出如此高昂的代价,从而证明它尚未实施。例如,检查这个虚构的声明:

class C<T> : T
{
}

正如 Eric Lippert 所注意到的,如果“如果 T 是结构怎么办?如果 T 是密封类类型怎么办?如果 T 是接口类型怎么办?如果 T 是 C 怎么办?!如果 T 是从 C 派生的类?如果 T 是具有抽象方法的抽象类型怎么办?如果 T 的可访问性低于 C 怎么办?如果 T 是 System.ValueType 怎么办?(你能有一个从 System.ValueType 继承的非结构吗?) System.Delegate、System.Enum 等呢?"

随着 Eric 的继续,“这些都是简单明了的”。的确,他是对的。我对一些既不简单也不明显的问题的具体示例感兴趣,这很难解决。

【问题讨论】:

  • C++ 比 C# 更复杂的原因是相互关联的,以及为什么您可以在 C++ 中完成您在 C# 中无法完成的事情。这就是为什么在可预见的未来,有些人会想用 C# 以外的东西做一些项目。
  • 我不明白:C# 编译器不应该在实际尝试实例化给定模板时已经知道这些问题的答案吗?例如,拥有类 C : T 并尝试在我的代码中编写 C i = new C() ,编译器不应该表现得好像我有一个类 C_int : int 和首先尝试 C_int i = new C_int() ? (请注意,在后一种情况下,编译器将抛出错误“不能从密封类 int 派生”——但它确实处理了这种情况)。为什么在尝试实例化模板之前必须回答所有这些问题?

标签: c# .net generics clr


【解决方案1】:

好吧,首先问问自己class C&lt;T&gt; : T { } 可能出了什么问题。很多事情立刻浮现在脑海:

如果 T 是一个结构怎么办?如果 T 是密封类类型怎么办?如果 T 是接口类型怎么办?如果 T 是C&lt;T&gt; 怎么办?!如果 T 是从C&lt;T&gt; 派生的类怎么办?如果 T 是具有抽象方法的抽象类型怎么办?如果 T 的可访问性低于 C 怎么办?如果 T 是 System.ValueType 怎么办? (你能有一个继承自 System.ValueType 的非结构吗?) System.Delegate、System.Enum 等等呢?

这些是简单而明显的。所提议的特性实际上打开了数百甚至数千个关于类型与其基本类型之间交互的更微妙的问题,所有这些都必须仔细指定、实现和测试。毫无疑问,我们会错过一些,从而导致未来发生重大变化,或者用实现定义的行为给运行时带来负担。

成本将是巨大的,因此收益最好是巨大的。我在这里没有看到巨大的好处。

【讨论】:

  • 你觉得这个功能对于 C++ 模板实现起来比较容易吗?因为它存在于那里,并且非常有用且异常强大。当我发现这个功能非常有用时,我可以举一个例子,因为这与我目前的工作有关。如果您想使用 Reflection.Emit 动态生成一个类型,该基类型仅在运行时已知,那么从其类型参数派生一个静态编译的泛型将非常有用,动态发出的类型可以从中派生.
  • C++ 模板的实现难度要小得多,因为 C++ 模板在每次构造时都会重新编译模板。 C# 泛型编译一次,如果类型参数是引用类型,则在运行时为所有构造生成相同的代码。这是根本的区别。这意味着 C# 泛型对于任何可能的构造都必须是正确的,而 C++ 模板只需要对于源代码中有限数量的实际构造是正确的。
  • 好点。但是泛型类型是开放类型。不能有任何开放泛型类型的实例。因此,在实际实例化泛型类型之前,它的实际内存布局可能仍未解决。事实上,即使在今天也是如此。聚合泛型参数类型实例的泛型类型在实例化之前具有不明确的内存布局。目前我不相信不可能提出一组约束(需要新类型的约束)来使讨论的继承工作。
  • @mark:Eric 已成功提供费用清单。您有一份福利清单。成本难以置信高。您要求弄乱类型系统。因此,为了成为一个有价值的功能,它必须对一小部分非常重要的项目(即,需要,而不仅仅是一个大障碍)有非常惊人的好处这需要可怕的痛苦和 p/invoke),或者对大量项目有惊人的好处。后者绝对不是真的;大多数程序员不知道(并且会故意保持不知道)这样做的任何理由,因此好处将无法实现。
  • @mark:据我所知,真正的需要通常不是从另一个本身继承泛型类型,而是有一种方法来创建包装并表现得像一个对象的东西其他类型的。如果接口中有问题的类型,则可以使用代理“真正”实现这种行为,但很尴尬;语言支持会更有帮助。
【解决方案2】:

好的,如果你不喜欢我之前的回答,那我们换个思路吧。

你的问题预设了一个错误:我们需要一个理由实现一个特性。相反,我们需要一个非常非常好的理由来实现任何功能。功能的前期成本、维护成本和机会成本都非常昂贵。 (也就是说,您花在功能 X 上的时间是您不能花在执行功能 Y 上的时间,这可能会阻止您执行功能 Z。)为了负责任地为我们的客户和利益相关者提供价值,我们无法实现每个功能有人碰巧喜欢。

运行时设计人员不能证明他们为什么没有实现您认为特别好的功能。功能的优先级是根据它们的成本与对用户的好处来确定的,而用户并没有完全敲我的门来要求这种继承。这个特殊的特性将极大地改变类型系统分析在运行时的工作方式,对每一种使用泛型的语言都有深远的影响,而且在我看来几乎没有提供什么好处。

我们在编译器中使用这种继承(用 C++ 编写),生成的代码难以遵循、难以维护,并且难以调试。我一直在尽我所能逐步消除这样的代码。我反对在 C# 中启用相同类型的不良模式,除非这样做会带来非常令人信服的好处。

以令人信服的方式描述这一巨大好处的任务在于需要该功能的人,而不是必须实施该功能的人。那么有什么引人注目的好处呢?

【讨论】:

  • 当然,我提出问题的方式可能会让人这么想。原因是我,作为一名前 C++ 开发人员,认为从模板参数类型继承的能力是一个非常有用的功能,在一些重要的库中广泛使用 - ATL 和 boost 等等(我不确定 STL使用它,可能)。尽管 C++ 模板和 .NET 泛型之间存在根本差异,但我很自然地期望 .NET 泛型也具有它。我很惊讶地发现他们没有。这就是为什么我认为 MS 想要这个功能,但选择不做。
  • 无论如何,我接受挑战并尝试收集令人信服的理由,说明为什么拥有此功能会很棒。为此,我将开始另一个社区 wiki 问题,并让人们提供原因。我会在发布后立即添加带有问题链接的评论。
  • 为什么我们不能在 .net 中使用 ATL 之类的东西,因为很少有人能理解它,因为它非常适合提高费率 :-)
  • “我们在编译器中使用这种继承”...您使用它的事实表明了它的用例,不是吗?
【解决方案3】:

代码示例,这可能会有所帮助:

public class SpecialDataRow<T> : T where T : DataRow
{
    public int SpecialFactor { get; set; }
}

这将允许从 DataRow 以及任何派生的 DataRows(如类型化数据集生成的)创建“特殊”行

我没有看到任何其他方式来编写这样的类

【讨论】:

  • 如果你想为多个后代操作 DataRow 添加方法,你可以使用扩展方法。
  • 好点。但是您会将这些方法添加到所有实例中,而不仅仅是那些“特殊”的实例(小问题,恕我直言)
  • Composition 非常适合这个。类似于: public class SpecialDataRow : DataRow { SpecialDataRow(DataRow Wrapped) {...} public int SpecialFactor { get;放; } /*普通的 DataRow 方法转发到包装 */ }
  • 这个。这是对泛型派生类进行编程的一种完全有效且理想的方式,并且在许多情况下都非常有用。通过强制使用特定的 where T:RootClass,您将消除上述其他帖子中提到的 99% 的问题。
  • @AndrewHanlon 但此代码无法编译。为什么这段代码不好?我认为这是非常有用的代码。
【解决方案4】:

这有什么用处?

请记住,尽管有这个名称,但泛型从未打算支持泛型编程。

要支持这样的功能,他们必须对 CLR 进行一些相当大的更改。

您需要定义一个派生自编译时甚至不存在的类型的类。

他们为什么要跳过这样的圈子,从根本上损害他们的类型系统,只是为了添加这个功能?值得吗?

如果您这么认为,请告诉他们原因。在 connect.microsoft.com 上写下反馈,告诉他们为什么这个功能如此重要以至于必须添加它。

【讨论】:

  • 如果您希望提供一种向大量类型添加功能的通用方法,这将是一个有用的功能。例如,一个与现有 IDisposable 类行为相同的类,除了 IDisposable 将具有一个“生命计数器”,该计数器将由“AddUser()”方法递增并由“Dispose”递减;只有当生命计数器达到零时,才会释放底层对象。
【解决方案5】:

C++ 模板无法与 C# 泛型进行比较。 C++ 模板像宏一样被预处理,而 .NET 中的泛型由运行时处理。

但是还有其他人比我更了解这...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-09
    • 2011-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多