为什么 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 进行数字排序时,通常需要实现 indexingIndexOf 来自 IList

想像Dictionary这样没有索引的集合,例如Dictionary中没有数字索引的概念。在Dictionary中,顺序不保证,key和value之间只有一对一的关系。因此,集合并不一定意味着数字索引。

另一个原因是因为IEnumerable并不是真正的双向交通。可以这样想:IEnumerable 可能会按照您指定的方式枚举项目 x 次并找到 x 处的元素(即 ElementAt),但它无法有效地知道如果它的任何元素位于哪个索引(即 IndexOf)。

但是,是的,即使你这样想它仍然很奇怪,因为它会 ElementAtIndexOf 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;
}