为什么C#(4.0)不允许泛型类类型中的协变和逆变?

为什么C#(4.0)不允许泛型类类型中的协变和逆变?,c#,generics,c#-4.0,covariance,contravariance,C#,Generics,C# 4.0,Covariance,Contravariance,限制的真正原因是什么?这只是必须完成的工作吗?这在概念上难吗?不可能吗 当然,不能在字段中使用类型参数,因为它们总是读写的。但这不是答案,不是吗 这个问题的原因是我正在写一篇关于C#4中的差异支持的文章,我觉得我应该解释为什么它只限于委托和接口。只是为了推卸举证责任 更新: 埃里克问了一个例子 那么这个呢(不知道这是否有意义,但:-) 公共类查找,其中T:Animal{ 公共查找(字符串名称){ 动物a=_cache.FindAnimalByName(名称); 返回a作为T; } } var f

限制的真正原因是什么?这只是必须完成的工作吗?这在概念上难吗?不可能吗

当然,不能在字段中使用类型参数,因为它们总是读写的。但这不是答案,不是吗

这个问题的原因是我正在写一篇关于C#4中的差异支持的文章,我觉得我应该解释为什么它只限于委托和接口。只是为了推卸举证责任

更新: 埃里克问了一个例子

那么这个呢(不知道这是否有意义,但:-)

公共类查找,其中T:Animal{
公共查找(字符串名称){
动物a=_cache.FindAnimalByName(名称);
返回a作为T;
}
}
var findReptiles=新查找();
查找:-)


更新2:我不是说CLR和C#应该允许这样做。只是想弄明白是什么导致了这一切,但事实并非如此

据我所知,CLR不支持此功能,因此添加此功能也需要在CLR方面进行大量工作。我相信在4.0版本之前,CLR实际上支持接口和委托的协变和逆变,因此这是一个相对简单的扩展


(不过,为类支持此功能肯定很有用!)

首先,正如Tomas所说,CLR不支持它

第二,这将如何运作?假设你有

class C<out T>
{ ... how are you planning on using T in here? ... }
C类
{…你打算怎么在这里使用T?}
T只能用于输出位置。正如您所注意到的,该类不能有任何类型为T的字段,因为该字段可以写入。该类不能有任何采用T的方法,因为这些方法是逻辑写入的。假设您有这个功能,您将如何利用它

如果我们可以,比如说,使T类型的只读字段合法化,那么这对于不可变类是有用的;这样我们就大大降低了它被不恰当地写入的可能性。但很难想出其他允许以类型安全方式进行差异的方案

如果你有这样的情况,我很想看看。这将是有朝一日在CLR中实现此功能的要点

更新:见


有关此问题的更多信息。

如果允许,可以定义有用的100%类型安全(无内部类型转换)类或结构,如果其构造函数接受一个或多个T或T供应商的类或结构,这些类或结构就其类型T而言是协变的。如果构造函数接受一个或多个T使用者,则可以定义与T相反的有用的100%类型安全类或结构。除了使用“new”而不是使用静态工厂方法(很可能来自名称与接口类似的类)之外,我不确定类相对于接口有多大优势,但我可以肯定地看到不可变结构支持协方差的用例。

你说得对。CLR 2.0带来了接口和委托的泛型类型参数的差异——只是不是在C中——好问题:)我记得去年在Lang.NET上Anders和一些Java编译器极客(抱歉)讨论过,Java家伙要求提供此功能。他似乎不知道他为什么问。但我不记得了。我想到了没有国家的班级。我会在这个问题上做些补充。对于不可变的数据类型,例如
Tuple
,这无疑是一个很好的特性,但我同意,这对于更改CLR:-)来说还不够令人信服。另一个用例是幻影类型,其中t仅用于类型安全,而不用于类的实现。懒惰类和任务类不适合在t上协变吗?他们在他们的公共API中只使用in-out位置。有很多例子。建筑工人呢?当然,这些是协变和逆变泛型的候选者。一个类的状态使它无法从泛型变化中获益的概念似乎很脆弱。它仅在state与类型var相对应时才有效,而类型var完全依赖于当前的类。换句话说,不难想象一个类会产生Ts或使用Ts,但不是两者都产生。而且,因为您可以考虑接口,在我看来,这并不意味着类不应该有差异。shrugSure,这是一个合理的例子,但您还没有展示任何不能用接口实现的功能。只需制作接口ILookup并让Lookup实现它。相对于接口差异,您的类差异场景增加了什么引人注目的额外好处?实际上没有。除此之外,它的代码更少。让我把举证责任倒过来。我们如何解释为什么不支持它。事实上,我不是要求实施它。“一直都是这样”不算数@Eric Lippert:我当然可以想象协变结构的用法。KeyValuePair怎么样?我们可以定义一个IKeyValPair,并用一个struct KeyValPair来实现它,但在许多使用场景中,这将导致非常可怕的装箱。@supercat:这是一个可以使其协变的类型的极好示例。这里的关键是数据类型在逻辑上是不可变的,因此您不必担心字段在构造函数中设置后会更改其值。如果我们有结构或类差异,那就是我想要开始的地方。@Eric Lippert:我知道你讨厌可变结构(顺便说一句,我刚刚打开了一个关于这个主题的聊天室),但是对于结构的协变正确性来说,不变性是必要的吗?传递给需要IEnumerable的例程的列表仍然是列表,但传递给需要KeyValuePair的例程的KeyValuePair将成为KeyValuePair。假设钥匙是可变的,把钥匙给狗会造成什么伤害?一个是存储数据
class C<out T>
{ ... how are you planning on using T in here? ... }