c#语言如何防止泛型变为协变,除非它们不包含需要T作为输入的方法?

Jas*_*dge 9 c# type-systems .net-4.5

我一直在阅读.NET4.5将带来的变化,在这篇博客文章中,我偶然发现了一些我既不知道也不理解的东西.

在谈到只读集合的​​实现时,Immo Landwerth说:

不幸的是,我们的类型系统不允许制作T协变类型,除非它没有将T作为输入的方法.因此,我们无法向IReadOnlyList添加IndexOf方法.我们认为,与不支持协方差相比,这是一个小小的牺牲.

从我明显有限的理解,似乎他说,为了使我们能够调用一个需要IReadOnlyList<Shape>通过传入IReadOnlyList<Circle>IReadOnlyList<T>.IndexOf(T someShape)方法,我们不能有一个方法.

我没有看到类型系统如何阻止它.谁能解释一下?

Jon*_*eet 11

假设Circle工具IEquatable<Circle>.IReadOnlyList<Circle>.IndexOf如果可以的话,这自然会被使用.现在,如果你能写下这个:

IReadOnlyList<Circle> circles = ...;
IReadOnlyList<Shape> shapes = circles;
int index = shapes.IndexOf(new Square(10));
Run Code Online (Sandbox Code Playgroud)

这最终会试图通过一个SquareCircle.Equals(Circle)这显然是一个坏主意.

强制执行" T输入位置无值"的规则在C#4规范的第13.1.3节中.你也应该阅读埃里克利珀对通用方差博客系列很多详细信息.


Dan*_*ker 6

由于IReadOnlyList<T>是协变的,你可以将其转换为任何超类型,T并且所有方法仍然应该根据合同工作.最超级的超类型TObject,所以如果IndexOf是界面的一部分,它应该已被接受Object.

正如Jon Skeet所说,在一个Circle对象列表中,您可以问:Square此列表中特定内容的索引是什么?唯一正确的响应是"它不在这里",并且IndexOf应该返回-1而不是抛出异常.

所以,我不同意Jon Skeet.鉴于协变通用参数的局限性,以及类似于ArrayList.indexOfJava,BCL团队应该包含一个IndexOf具有以下签名的方法:

int IndexOf(object item);
Run Code Online (Sandbox Code Playgroud)

完全相同的参数适用ContainsIReadOnlyCollection:当您传入一个不兼容类型的对象时,该集合显然不包含它,并且该方法应该只返回false.

唯一的缺点是拳击值类型,但实际的实现仍然可以隐藏IReadOnlyList.IndexOf方法并提供自己的泛型重载,使这个论点没有实际意义.

因此,IndexOf在传递不兼容的对象(如果它在接口上)时,您希望返回-1 是正确的.


我在我的M42.Collections库中实现了这个原则,以说明这在实践中是如何工作的.您可以在这里下载:

M42 Collections - 用于正确处理集合的可移植.NET库.