24

我正在处理一个IReadOnlyCollection对象。

现在我有点惊讶,因为我可以使用linq扩展方法ElementAt()。但我无权访问IndexOf().

这在我看来有点不合逻辑:我可以在给定位置获取元素,但我无法获取相同元素的位置。

有什么具体原因吗?

我已经阅读过 ->如何获取 IEnumerable 中元素的索引?我对回应并不完全满意。

4

5 回答 5

39

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

于 2020-02-20T09:00:12.120 回答
4

IndexOf是在 上定义的方法List,而IReadOnlyCollection仅继承IEnumerable.

这是因为IEnumerable仅用于迭代实体。但是,索引不适用于此概念,因为顺序是任意的,并且不能保证调用IEnumerable. 此外,该界面仅声明您可以迭代集合,而List声明您也可以执行添加和删除。

ElementAt方法确实做到了这一点。但是我不会使用它,因为它会重复整个枚举以找到一个元素。更好地使用First或只是基于列表的方法。

无论如何,API 设计对我来说似乎很奇怪,因为它允许在第n个位置获取元素的(低效)方法,但不允许获取任意元素的索引,这将是相同的低效搜索,导致最多n次迭代. 我会同意伊恩的观点(我不推荐),或者两者都不同意。

于 2016-05-25T08:40:50.217 回答
4

这是因为IReadOnlyCollection(实现IEnumerable)不一定实现indexing,当您想对 a 进行数字排序时通常需要实现ListIndexOf是从IList

想想一个没有索引的集合Dictionary,例如,没有数字索引的概念Dictionary。在Dictionary中,不保证顺序,键和值之间只有一一对应的关系。因此,集合不一定意味着数字索引。

另一个原因是因为IEnumerable不是真正的两种方式的交通。可以这样想:可以按您指定IEnumerable的时间枚举项目并在(即,)处找到元素,但它无法有效地知道其任何元素是否位于哪个索引(即,)中。xxElementAtIndexOf

但是,是的,即使您以这种方式思考它仍然很奇怪,因为它期望它同时具有没有ElementAtIndexOf

于 2016-05-25T08:41:15.197 回答
3

IReadOnlyCollection<T>hasElementAt<T>()因为它是 的扩展IEnumerable<T>,它具有该方法。 ElementAt<T>()迭代IEnumerable<T>指定次数的迭代并返回值作为该位置。

IReadOnlyCollection<T>缺少IndexOf<T>(),因为作为IEnumerable<T>,它没有任何指定的顺序,因此索引的概念不适用。也没有IReadOnlyCollection<T>添加任何秩序的概念。

当你想要IReadOnlyList<T>一个可索引版本的IReadOnlyCollection<T>. 这允许您正确地表示具有索引的不可更改的对象集合。

于 2019-01-29T20:29:18.283 回答
1

这可能对某人有帮助:

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;
}
于 2020-07-10T13:52:47.413 回答