我正在处理一个IReadOnlyCollection
对象。
现在我有点惊讶,因为我可以使用linq
扩展方法ElementAt()
。但我无权访问IndexOf()
.
这在我看来有点不合逻辑:我可以在给定位置获取元素,但我无法获取相同元素的位置。
有什么具体原因吗?
我已经阅读过 ->如何获取 IEnumerable 中元素的索引?我对回应并不完全满意。
我正在处理一个IReadOnlyCollection
对象。
现在我有点惊讶,因为我可以使用linq
扩展方法ElementAt()
。但我无权访问IndexOf()
.
这在我看来有点不合逻辑:我可以在给定位置获取元素,但我无法获取相同元素的位置。
有什么具体原因吗?
我已经阅读过 ->如何获取 IEnumerable 中元素的索引?我对回应并不完全满意。
IReadOnlyCollection
是一个集合,而不是一个列表,所以严格来说,它甚至不应该有ElementAt()
. 这个方法是IEnumerable
为了方便而定义的,并且IReadOnlyCollection
拥有它是因为它继承自IEnumerable
. 如果您查看源代码,它会检查 是否IEnumerable
实际上是 a IList
,如果是,则返回请求索引处的元素,否则继续对IEnumerable
直到请求索引进行线性遍历,这是低效的。
所以,你可能会问为什么IEnumerable
有一个ElementAt()
但没有 IndexOf()
,但我觉得这个问题不是很有趣,因为它不应该有这两种方法。AnIEnumerable
不应该是可索引的。
现在,一个非常有趣的问题是为什么两者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>
extend IList<T>
,因为那样它会继承突变方法,所以他们基于IReadOnlyCollection<T>
andIEnumerable<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 在评论中指出这一点。)
IndexOf
是在 上定义的方法List
,而IReadOnlyCollection
仅继承IEnumerable
.
这是因为IEnumerable
仅用于迭代实体。但是,索引不适用于此概念,因为顺序是任意的,并且不能保证调用IEnumerable
. 此外,该界面仅声明您可以迭代集合,而List
声明您也可以执行添加和删除。
该ElementAt
方法确实做到了这一点。但是我不会使用它,因为它会重复整个枚举以找到一个元素。更好地使用First
或只是基于列表的方法。
无论如何,API 设计对我来说似乎很奇怪,因为它允许在第n个位置获取元素的(低效)方法,但不允许获取任意元素的索引,这将是相同的低效搜索,导致最多n次迭代. 我会同意伊恩的观点(我不推荐),或者两者都不同意。
这是因为IReadOnlyCollection
(实现IEnumerable
)不一定实现indexing
,当您想对 a 进行数字排序时通常需要实现List
。IndexOf
是从IList
。
想想一个没有索引的集合Dictionary
,例如,没有数字索引的概念Dictionary
。在Dictionary
中,不保证顺序,键和值之间只有一一对应的关系。因此,集合不一定意味着数字索引。
另一个原因是因为IEnumerable
不是真正的两种方式的交通。可以这样想:可以按您指定IEnumerable
的时间枚举项目并在(即,)处找到元素,但它无法有效地知道其任何元素是否位于哪个索引(即,)中。x
x
ElementAt
IndexOf
但是,是的,即使您以这种方式思考它仍然很奇怪,因为它期望它同时具有或没有。ElementAt
IndexOf
IReadOnlyCollection<T>
hasElementAt<T>()
因为它是 的扩展IEnumerable<T>
,它具有该方法。 ElementAt<T>()
迭代IEnumerable<T>
指定次数的迭代并返回值作为该位置。
IReadOnlyCollection<T>
缺少IndexOf<T>()
,因为作为IEnumerable<T>
,它没有任何指定的顺序,因此索引的概念不适用。也没有IReadOnlyCollection<T>
添加任何秩序的概念。
当你想要IReadOnlyList<T>
一个可索引版本的IReadOnlyCollection<T>
. 这允许您正确地表示具有索引的不可更改的对象集合。
这可能对某人有帮助:
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;
}