9

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

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

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

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

我看不出类型系统会如何阻止这种情况。有人可以解释吗?

4

3 回答 3

11

假设Circle实现IEquatable<Circle>. IReadOnlyList<Circle>.IndexOf如果它可用,它自然会被使用。现在,如果你可以这样写:

IReadOnlyList<Circle> circles = ...;
IReadOnlyList<Shape> shapes = circles;
int index = shapes.IndexOf(new Square(10));

那最终会试图将 a 传递SquareCircle.Equals(Circle)这显然是一个坏主意。

强制“输入位置没有值”的规则T在 C# 4 规范的第 13.1.3 节中。您还应该阅读Eric Lippert 关于泛型方差的博客系列以了解更多详细信息。

于 2012-06-18T12:11:17.300 回答
6

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

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

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

int IndexOf(object item);

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

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

因此,如果在接口上传递不兼容的对象,您期望IndexOf返回 -1 是正确的。


我在我的M42.Collections库中实现了这个原则,以展示它在实践中是如何工作的。你可以在这里下载:

M42 Collections - 用于正确处理集合的便携式 .NET 库。

于 2013-01-17T01:00:22.987 回答
1

请注意,理论上(不确定是否在 .NET 中实现)是正确的,IndexOf为 的所有子类型定义T

class ReadOnlyList<+T> = { ...
  Int IndexOf<U>(U elem) where T : U { ... }
}

有趣的是为什么IndexOf(T elem)不明显是协变的,而这个是:如果你有T2 : T1,一个方法IndexOf(T1 elem)可能与签名不兼容IndexOf(T2 elem)。相反,如果你有IndexOf<U>(U elem) where T1 : U,你知道T2 : T1and T1 : U,所以你也有T2 : U所有这些U:这种类型是 的子类型IndexOf<U>(U elem) with T2 : U

例如,在 Emir、Kennedy、Russo 和 Yu 的 2006 年文章Variance and Generalized Constraints for C# Generics 中就有这样的评论:他们提出了一个可以接受这个定义的类型系统。

于 2012-06-24T15:38:00.350 回答