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 中指出这一点。)