为什么 IReadOnlyCollection 有 ElementAt 但没有 IndexOf
Why IReadOnlyCollection has ElementAt but not IndexOf
我正在处理 IReadOnlyCollection
个对象。
现在我有点惊讶,因为我可以使用 linq
扩展方法 ElementAt()
。但我无权访问 IndexOf()
.
这对我来说看起来有点不合逻辑:我可以在给定位置获取元素,但我无法获取相同元素的位置。
有具体原因吗?
我已经阅读 -> How to get the index of an element in an IEnumerable? 但我对回复不是很满意。
IndexOf
是在 List
上定义的方法,而 IReadOnlyCollection
仅继承 IEnumerable
.
这是因为 IEnumerable
仅用于迭代实体。但是,索引不适用于此概念,因为顺序是任意的,并且不能保证对 IEnumerable
的调用之间的顺序相同。此外,该界面只是说明您可以 迭代 一个集合,而 List
说明您还可以执行添加和删除操作。
ElementAt
方法确实可以做到这一点。但是我不会使用它,因为它会重复整个枚举以找到一个元素。最好使用 First
或仅使用基于列表的方法。
无论如何,API 设计对我来说似乎很奇怪,因为它允许一种(低效的)方法来获取第 n 位置的元素,但不允许获取任意元素的索引,这将是相同的低效搜索,导致最多 n 次迭代。我同意 Ian 的观点,要么两者都同意(我不推荐),要么两者都不同意。
这是因为 IReadOnlyCollection
(实现了 IEnumerable
)不一定实现 indexing
,当你想对 List
进行数字排序时,通常需要实现 indexing
。 IndexOf
来自 IList
。
想像Dictionary
这样没有索引的集合,例如Dictionary
中没有数字索引的概念。在Dictionary
中,顺序不保证,key和value之间只有一对一的关系。因此,集合并不一定意味着数字索引。
另一个原因是因为IEnumerable
并不是真正的双向交通。可以这样想:IEnumerable
可能会按照您指定的方式枚举项目 x
次并找到 x
处的元素(即 ElementAt
),但它无法有效地知道如果它的任何元素位于哪个索引(即 IndexOf
)。
但是,是的,即使你这样想它仍然很奇怪,因为它会 ElementAt
和 IndexOf
或 none.
IReadOnlyCollection<T>
有 ElementAt<T>()
因为它是 IEnumerable<T>
的扩展,它有那个方法。 ElementAt<T>()
迭代 IEnumerable<T>
指定的迭代次数和 returns 值作为该位置。
IReadOnlyCollection<T>
缺少 IndexOf<T>()
,因为作为 IEnumerable<T>
,它没有任何指定的顺序,因此索引的概念不适用。 IReadOnlyCollection<T>
也没有添加任何顺序概念。
如果您想要 IReadOnlyCollection<T>
的可索引版本,我会推荐 IReadOnlyList<T>
。这使您可以使用索引正确地表示不可更改的对象集合。
IReadOnlyCollection
是集合,不是列表,所以严格来说应该连ElementAt()
都没有。这个方法在IEnumerable
中定义是为了方便,IReadOnlyCollection
有它是因为它继承自IEnumerable
。如果您查看源代码,它会检查 IEnumerable
实际上是否是 IList
,如果是,它会 returns 请求索引处的元素,否则它会继续执行线性遍历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 在评论中指出这一点。)
这个扩展方法和Mike的几乎一样。唯一的区别是它包含一个谓词,所以你可以这样使用它:var index = list.IndexOf(obj => obj.Id == id)
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;
}
我正在处理 IReadOnlyCollection
个对象。
现在我有点惊讶,因为我可以使用 linq
扩展方法 ElementAt()
。但我无权访问 IndexOf()
.
这对我来说看起来有点不合逻辑:我可以在给定位置获取元素,但我无法获取相同元素的位置。
有具体原因吗?
我已经阅读 -> How to get the index of an element in an IEnumerable? 但我对回复不是很满意。
IndexOf
是在 List
上定义的方法,而 IReadOnlyCollection
仅继承 IEnumerable
.
这是因为 IEnumerable
仅用于迭代实体。但是,索引不适用于此概念,因为顺序是任意的,并且不能保证对 IEnumerable
的调用之间的顺序相同。此外,该界面只是说明您可以 迭代 一个集合,而 List
说明您还可以执行添加和删除操作。
ElementAt
方法确实可以做到这一点。但是我不会使用它,因为它会重复整个枚举以找到一个元素。最好使用 First
或仅使用基于列表的方法。
无论如何,API 设计对我来说似乎很奇怪,因为它允许一种(低效的)方法来获取第 n 位置的元素,但不允许获取任意元素的索引,这将是相同的低效搜索,导致最多 n 次迭代。我同意 Ian 的观点,要么两者都同意(我不推荐),要么两者都不同意。
这是因为 IReadOnlyCollection
(实现了 IEnumerable
)不一定实现 indexing
,当你想对 List
进行数字排序时,通常需要实现 indexing
。 IndexOf
来自 IList
。
想像Dictionary
这样没有索引的集合,例如Dictionary
中没有数字索引的概念。在Dictionary
中,顺序不保证,key和value之间只有一对一的关系。因此,集合并不一定意味着数字索引。
另一个原因是因为IEnumerable
并不是真正的双向交通。可以这样想:IEnumerable
可能会按照您指定的方式枚举项目 x
次并找到 x
处的元素(即 ElementAt
),但它无法有效地知道如果它的任何元素位于哪个索引(即 IndexOf
)。
但是,是的,即使你这样想它仍然很奇怪,因为它会 ElementAt
和 IndexOf
或 none.
IReadOnlyCollection<T>
有 ElementAt<T>()
因为它是 IEnumerable<T>
的扩展,它有那个方法。 ElementAt<T>()
迭代 IEnumerable<T>
指定的迭代次数和 returns 值作为该位置。
IReadOnlyCollection<T>
缺少 IndexOf<T>()
,因为作为 IEnumerable<T>
,它没有任何指定的顺序,因此索引的概念不适用。 IReadOnlyCollection<T>
也没有添加任何顺序概念。
如果您想要 IReadOnlyCollection<T>
的可索引版本,我会推荐 IReadOnlyList<T>
。这使您可以使用索引正确地表示不可更改的对象集合。
IReadOnlyCollection
是集合,不是列表,所以严格来说应该连ElementAt()
都没有。这个方法在IEnumerable
中定义是为了方便,IReadOnlyCollection
有它是因为它继承自IEnumerable
。如果您查看源代码,它会检查 IEnumerable
实际上是否是 IList
,如果是,它会 returns 请求索引处的元素,否则它会继续执行线性遍历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 在评论中指出这一点。)
这个扩展方法和Mike的几乎一样。唯一的区别是它包含一个谓词,所以你可以这样使用它:var index = list.IndexOf(obj => obj.Id == id)
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;
}