为什么C# / CLR不支持方法重写的协变/逆变?

13

有很多关于绕过C#不允许方法返回(和参数)类型在重写时更改为兼容类型的限制的问题和答案,但是 为什么 要存在这种限制,无论是在C#编译器还是CLR中?据我所知,如果允许共变性/逆变性,没有任何可能会导致错误的情况,因此背后的原因是什么呢?

类似的问题也可以针对扩大访问参数进行提问-例如使用公共方法覆盖受保护的内部方法(Java支持此操作,如果我没记错的话)。

5个回答

3

3

Eric Lippert已经比我回答得更好了。

请查看他关于C#中协变和逆变的系列文章Covariance and Contravariance in C#

以及

How does C# 4.0 Generic Covariance & Contra-variance Implmeneted?

编辑:Eric指出他并没有讨论返回类型协变,但我决定在这个答案中保留链接,因为这是一系列很棒的文章,如果有人查找这个主题可能会发现它有用。

这个功能已经被请求, 近5年前,Microsoft回应说:“感谢您记录这个。我们经常听到这个请求。我们将考虑在下一个版本中实现它。”

现在我引用Jon Skeet的话,因为如果没有Jon Skeet的答案,StackOverflow上就不会是一个合适的答案。 Covariance and void return types

我强烈怀疑答案在CLR的实现中而不是在深层语义原因中 - CLR可能需要知道是否会有返回值,以便对栈进行适当处理。即使如此,在优雅方面似乎有点可惜。我不能说我在现实生活中曾经有过这种需求,而且在.NET 3.5中可以通过编写从Func<X>Action<X>Func<X,Y>Action<X,Y>等的转换器来伪造(最多四个参数)。但这有点令人不爽 :)


我知道"implmenedted"拼写错误了。但是我只是直接链接到它。 - Robert Kozak
6
不,我从未回答过这个问题。那些文章是关于通用类型的变异。这个问题涉及返回类型的协变性。在我关于通用类型变异的文章中,我明确指出我没有谈论返回类型变异。 - Eric Lippert
抱歉,我的记忆力不太好。我几个月前读过它们,但在回答之前没有重新阅读它们。 :) - Robert Kozak
1
我不是指void类型和其他对象类型之间的协变,而是指方法覆盖上的协变 - 用返回对象的方法覆盖一个返回字符串的方法。 - thecoop

0

似乎引入返回值的协变性没有Java和C++使用的本质缺陷。然而,引入形式参数的逆变性确实会导致真正的混淆。我认为这个C++中的答案 https://dev59.com/p3A75IYBdhLWcg3w_ef1#3010614 对于C#也是有效的。


-1

有的,你只需要等待VS2010/.Net 4.0。


这仅适用于接口,而不适用于具体类型。 - JaredPar
但是,最初为什么要把它排除在外呢?它似乎是一个非常有用的东西,例如工厂模式。 - thecoop
1
@JaredPar - 接口 + 委托 ;-p - Marc Gravell
1
但是一开始为什么要把它排除在外呢? - Levi
5
这是不正确的。C# 4.0 没有添加返回类型协变性。我们添加了泛型类型的变异性,这是完全不同的。 - Eric Lippert

-1
扩展Joel的回答 - CLR长期支持有限变性,但C#编译器直到4.0才使用新的泛型接口和委托上的“in”和“out”修饰符。原因很复杂,我会陷入混乱,尝试解释,但它并不像看起来那么简单。
将“protected internal”方法转换为“public”方法可以使用方法隐藏实现:
public new void Foo(...) { base.Foo(...); }

只要参数等也是公共的,这样使用可以吗?


是的,CLR 2 支持泛型类型的变异。但它不支持返回类型协变。 - Eric Lippert
方法隐藏可以起作用,但它不会像虚方法一样运行 - 对超类型实例的调用不会分派到公共覆盖。 - thecoop
1
确实,它有限制...因为你不能在同一类型中覆盖和“new”相同的签名,所以除非引入额外类型和额外虚拟调用,否则无法实现正确的虚拟。 - Marc Gravell
1
不好的答案。泛型的协变性不等于重写的协变性;方法隐藏不等于重写。 - Qwertie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接