为啥 IReadOnlyCollection 有 ElementAt 但没有 IndexOf
Posted
技术标签:
【中文标题】为啥 IReadOnlyCollection 有 ElementAt 但没有 IndexOf【英文标题】:Why IReadOnlyCollection has ElementAt but not IndexOf为什么 IReadOnlyCollection 有 ElementAt 但没有 IndexOf 【发布时间】:2016-09-22 17:53:32 【问题描述】:我正在处理 IReadOnlyCollection
的对象。
现在我有点惊讶,因为我可以使用linq
扩展方法ElementAt()
。但我无权访问IndexOf()
。
这在我看来有点不合逻辑:我可以获取给定位置的元素,但无法获取相同元素的位置。
有什么具体原因吗?
我已经阅读了 -> How to get the index of an element in an IEnumerable?,但我对回复并不完全满意。
【问题讨论】:
【参考方案1】:IReadOnlyCollection
是一个集合,而不是一个列表,所以严格来说,它甚至不应该有ElementAt()
。这个方法在IEnumerable
中定义是为了方便,IReadOnlyCollection
有它是因为它继承自IEnumerable
。如果查看源代码,它会检查IEnumerable
是否实际上是IList
,如果是,则返回请求索引处的元素,否则继续对IEnumerable
进行线性遍历,直到请求索引,效率低。
所以,你可能会问为什么IEnumerable
有ElementAt()
而没有IndexOf()
,但我觉得这个问题不是很有趣,因为它不应该有这两种方法。 IEnumerable
不应该是可索引的。
现在,一个非常有趣的问题是为什么IReadOnlyList
也没有IndexOf()
。
IReadOnlyList<T>
没有 IndexOf()
没有任何理由。
如果真要找个理由提,那理由就是历史了:
早在 90 年代中期,当 C# 被淘汰时,人们还没有完全开始意识到不可变性和只读性的好处,所以不幸的是,他们融入语言的 IList<T>
接口是可变的。
正确的做法是提出IReadOnlyList<T>
作为基本接口,并让IList<T>
扩展它,只添加变异方法,但事实并非如此。
IReadOnlyList<T>
是在 IList<T>
之后相当长的一段时间内发明的,到那时重新定义 IList<T>
并使其扩展 IReadOnlyList<T>
为时已晚。所以,IReadOnlyList<T>
是从零开始构建的。
他们不能让IReadOnlyList<T>
扩展IList<T>
,因为那样它会继承突变方法,所以他们基于IReadOnlyCollection<T>
和IEnumerable<T>
代替。他们添加了this[i]
索引器,但是他们要么忘记添加其他方法,如IndexOf()
,要么故意省略它们,因为它们可以作为扩展方法实现,从而使接口更简单。 但是他们没有提供任何这样的扩展方法。
所以,这里是一个扩展方法,将IndexOf()
添加到IReadOnlyList<T>
:
using Collections = System.Collections.Generic;
public static int IndexOf<T>( this Collections.IReadOnlyList<T> self, T elementToFind )
int i = 0;
foreach( T element in self )
if( Equals( element, elementToFind ) )
return i;
i++;
return -1;
请注意,此扩展方法不如接口中内置的方法强大。例如,如果您正在实现一个期望 IEqualityComparer<T>
作为构造(或单独的)参数的集合,则此扩展方法将完全不知道它,这当然会导致错误。 (感谢 Grx70 在 cmets 中指出这一点。)
【讨论】:
它总是让我感到好笑,*** 上的答案如何试图为某个 API 以某种方式或另一种方式提供理由,好像 API 总是正确的,好像它是用完美的方式构建的智慧,因此必须始终找到一些理由并向提出问题的假定新手解释。 (谁通常根本不是新手,他们通常会因为看到API有问题而提出问题,并且通过提出问题来指出问题总是被认为比大吵大闹,这是我通常喜欢做的事情。) 许多 .NET API 因历史原因而变得混乱且过于复杂。我希望看到任何后续主要版本的干净利落。兼容性很好,但不以拖累 90 年代的设计错误为代价 我同意这个答案的主要建议,即IReadOnlyList<T>
上缺少int IndexOf(T)
方法没有充分的理由。然而,建议的解决方法存在缺陷,我不同意这种方法不必包含在接口中的说法,因为它可以用扩展方法“替换”。想象一个使用任意IEqualityComparer<T>
的集合,您可能会在此扩展名返回-1
而Contains(T)
返回true
(或相反),这可能会导致麻烦。
@Grx70 我不确定我是否理解您检测到的缺陷是什么。您是指我的函数缺少接受IEqualityComparer<T>
的重载这一事实吗?您是指它似乎没有使用IEquatable<T>
的事实吗?我可以添加这些,但我认为最好将它们作为练习留给读者。足够先进以实际使用这些接口的程序员会发现这很简单。还有什么吗?【参考方案2】:
这是因为IReadOnlyCollection
(实现IEnumerable
)不一定实现indexing
,当您想以数字方式订购List
时通常需要这样做。 IndexOf
来自 IList
。
想想像Dictionary
这样没有索引的集合,Dictionary
中没有数字索引的概念。在Dictionary
中,不保证顺序,key和value之间只有一一对应的关系。因此,集合并不一定意味着数字索引。
另一个原因是因为IEnumerable
并不是真正的双向流量。可以这样想:IEnumerable
可能会在您指定时枚举项目x
并在x
(即ElementAt
)处找到元素,但它无法有效地知道它的任何元素是否位于哪个索引(即IndexOf
)。
但是,是的,即使您以这种方式认为它仍然很奇怪,因为期望它同时具有 either ElementAt
和 IndexOf
or none。
【讨论】:
【参考方案3】:IndexOf
是在List
上定义的方法,而IReadOnlyCollection
仅继承IEnumerable
。
这是因为IEnumerable
仅用于迭代实体。然而,索引不适用于这个概念,因为顺序是任意的,并且不能保证在对IEnumerable
的调用之间是相同的。此外,界面只是声明您可以迭代一个集合,而List
声明您也可以执行添加和删除操作。
ElementAt
方法确实可以做到这一点。但是我不会使用它,因为它会重复整个枚举以找到一个元素。最好使用First
或仅使用基于列表的方法。
无论如何,API 设计对我来说似乎很奇怪,因为它允许在第 n 位置获取元素的(低效)方法,但不允许获取任意元素的索引,这将是同样的低效搜索导致多达 n 次迭代。我会同意 Ian 的观点(我不推荐)或两者都不同意。
【讨论】:
我完全同意您的观点,即 Enumerables 不应该通过索引访问,但我仍然可以使用 ElementAt()。对我来说,我在给定位置获得元素有点愚蠢,但不是元素的位置。这更像是一个哲学观点:) 这确实是一个很奇怪的方法。我认为它的存在只是因为任何人都需要这个未来,但是直到现在,人们对IndexOf
-method 的***并不高。【参考方案4】:
IReadOnlyCollection<T>
具有 ElementAt<T>()
,因为它是 IEnumerable<T>
的扩展,具有该方法。 ElementAt<T>()
迭代 IEnumerable<T>
指定的迭代次数并返回值作为该位置。
IReadOnlyCollection<T>
缺少IndexOf<T>()
,因为作为IEnumerable<T>
,它没有任何指定的顺序,因此索引的概念不适用。 IReadOnlyCollection<T>
也没有添加任何顺序的概念。
如果您想要IReadOnlyCollection<T>
的可索引版本,我会推荐IReadOnlyList<T>
。这允许您正确地表示具有索引的不可更改的对象集合。
【讨论】:
“不要使用它”并不是一个真正的答案,但你所说的有一些价值。也许您可以通过指出ICollection<T>
也没有IndexOf()
方法,而IList<T>
有方法,让这个看起来更像一个答案。所以这只是语义问题,即集合被视为无序包(可能,但这始终是这些“为什么”问题的问题:谁知道?)。
@GertArnold 你是对的。我依靠用户最后阅读我的评论。既然您已经指出了这一点,我就可以看到问题所在。我将编辑我的答案以使其更完整。
主要问题是IReadOnlyList<T>
also 没有实现IndexOf()
函数。 IReadOnlyCollection
没有它没什么大不了的,因为它没有索引语义,但使用列表的全部意义在于它应该是有序和可索引的。 List<T>
和 IList<T>
都有,但 IReadOnlyList
没有。这很令人沮丧。【参考方案5】:
这可能对某人有帮助:
public static int IndexOf<T>(this IReadOnlyList<T> self, Func<T, bool> predicate)
for (int i = 0; i < self.Count; i++)
if (predicate(self[i]))
return i;
return -1;
【讨论】:
嗨,欢迎来到 ***!感谢您的贡献。请查看 Mike Nakis 的答案,并考虑您的答案是否有点重复。如果不是,请编辑您的答案并提供一些额外的解释和调用您的函数的代码示例,尤其是关于谓词参数。谢谢! 和迈克的差不多。唯一的区别是它使用谓词,所以你可以像这样使用它: var index = list.IndexOf(obj => obj.Id == id)以上是关于为啥 IReadOnlyCollection 有 ElementAt 但没有 IndexOf的主要内容,如果未能解决你的问题,请参考以下文章
IReadOnlyCollection 上的隐式/显式转换混淆
不变性/只读语义(特别是 C# IReadOnlyCollection<T>)
List<T>.AsReadOnly() 与 IReadOnlyCollection<T>
对参数使用 IReadOnlyCollection<T> 而不是 IEnumerable<T> 以避免可能的多次枚举
有没有办法将 IReadOnlyCollection<T>/IReadOnlyList<T> 与 protobuf-net 一起使用
无法将 Dictionary<string, List<string>> 转换为 IReadOnlyDictionary<string, IReadOnlyCollect