Почему IReadOnlyCollection имеет ElementAt, но не IndexOf

Я работаю с IReadOnlyCollection объектами.

Теперь я немного удивлен, потому что могу использовать linq метод расширения ElementAt(). Но у меня нет доступа к IndexOf().

Мне это кажется немного нелогичным: я могу получить элемент в заданной позиции, но я не могу получить позицию того же самого элемента.

Есть ли для этого конкретная причина?

Я уже читал -> Как получить индекс элемента в IEnumerable?, и я не совсем доволен ответом.


person Lorenzo Santoro    schedule 25.05.2016    source источник


Ответы (5)


IReadOnlyList<T> has no IndexOf() for no good reason whatsoever.

Если вы действительно хотите найти повод для упоминания, то причина историческая:

Еще в середине девяностых, когда был основан 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 за указание на это в комментариях.)

person Mike Nakis    schedule 20.02.2020
comment
Меня всегда забавляет, как ответы на stackoverflow пытаются обосновать, почему тот или иной API работает тем или иным образом, как если бы API всегда был правильным, как если бы он был построен с совершенной мудростью, поэтому всегда должно быть какое-то оправдание. его нужно найти и объяснить предполагаемому новичку, задавшему вопрос. - person Mike Nakis; 20.02.2020
comment
(Кто обычно не новичок, и они обычно задают вопрос, потому что видят, что есть проблема с API, и указание на проблему, задав вопрос, всегда считается более вежливым, чем разглагольствовать об этом, что я вообще предпочитаю делать.) - person Mike Nakis; 20.02.2020
comment
Многие API-интерфейсы .NET могут быть запутанными и чрезмерно сложными по историческим причинам. Я хотел бы видеть чистую версию любого следующего крупного релиза. Совместимость - это хорошо, но не ценой перетаскивания ошибок дизайна из 90-х годов. - person Toxantron; 16.06.2020
comment
Я согласен с основным предложением этого ответа, что нет веской причины для отсутствия метода int IndexOf(T) на IReadOnlyList<T>. Однако предлагаемый обходной путь имеет недостаток, и я не согласен с утверждением, что этот метод не обязательно должен быть включен в интерфейс, потому что его можно заменить методом расширения. Представьте себе коллекцию, в которой используется произвольный IEqualityComparer<T>, и вы можете закончить тем, что это расширение будет возвращать -1, а Contains(T) возвращает true (или наоборот), что, скорее всего, приведет к проблемам. - person Grx70; 09.08.2020
comment
@ Grx70 Я не уверен, что понимаю, в чем заключается недостаток, который вы обнаружили. Вы имеете в виду тот факт, что в моей функции отсутствует перегрузка, которая принимает IEqualityComparer<T>? Вы имеете в виду тот факт, что, похоже, он не использует IEquatable<T>? Я мог бы добавить их, но я думаю, что их лучше оставить читателю в качестве упражнения. Программисты, достаточно продвинутые, чтобы реально использовать эти интерфейсы, сочтут это тривиальным. Есть ли еще что-нибудь? - person Mike Nakis; 10.08.2020
comment
@MikeNakis Позвольте мне привести вам пример. На самом деле я наткнулся на этот вопрос при реализации настраиваемого упорядоченного набора (ISet<T>, который поддерживает индексацию, т.е. также реализует IReadOnlyList<T>). Очевидно, мой класс принимает произвольные IEqualityComparer<T>. Скажем, мой класс является внутренним, как и компаратор, который я использую, и я показываю его вам только как IReadOnlyList<T> через внедрение зависимостей. Тогда невозможно написать метод расширения, который всегда возвращал бы правильные результаты, потому что у вас нет возможности получить доступ к компаратору или даже зная, что я использую собственный. - person Grx70; 10.08.2020
comment
@MikeNakis Я хочу сказать, что IReadOnlyList<T> сам по себе не предоставляет достаточно информации для реализации метода расширения, который охватывал бы все возможные сценарии. Конечно, вы можете принять дополнительные параметры, но это точно показывает, что вам нужна дополнительная информация, которую IReadOnlyList<T> не предоставляет. - person Grx70; 10.08.2020
comment
@ Grx70 да, понятно. Я изменил свой ответ, чтобы указать на оговорку, на которую вы указываете. Итак, каков был бы выход из вашей ситуации? Я полагаю, вам нужно будет выставить IList<T> вместо IReadOnlyList<T>, чтобы в интерфейсе был IndexOf(), который вы можете реализовать, чтобы использовать свой IEqualityComparer<T>, верно? - person Mike Nakis; 10.08.2020
comment
@MikeNakis Думаю, нет единственно правильного подхода к этому. Вы можете либо расширить IReadOnlyList<T>, чтобы включить IndexOf(T), либо реализовать IList<T> с IsReadOnly, возвращающим true, и всеми методами изменения, генерирующими исключение. Моя цель, однако, состояла только в том, чтобы аргументировать, почему IndexOf(T) должен быть частью IReadOnlyList<T>. - person Grx70; 10.08.2020
comment
Я сам пропустил этот нюанс в первый раз, но исходный вопрос был о IReadOnlyCollection<T>, а не о IReadOnlyList<T>. Списки расширяют коллекции, а не наоборот, и коллекции действительно не хватает индексированного доступа - намеренно, а не случайно. Этот ответ, вероятно, верен для списков, доступных только для чтения; нелогично, что у них есть индексированный доступ, но нет IndexOf. Однако это не ответ на тот же вопрос. IReadOnlyList<T> - это гораздо более новая концепция, чем IReadOnlyCollection<T> - я не думаю, что первое существовало еще в 2016 году. - person Aaronaught; 07.06.2021

Это потому, что IReadOnlyCollection (который реализует IEnumerable) не обязательно реализует indexing, что часто требуется, когда вы хотите численно упорядочить List. IndexOf из IList.

Подумайте о коллекции без индекса, например о Dictionary, в Dictionary нет понятия числового индекса. В Dictionary порядок не гарантируется, только отношение один к одному между ключом и значением. Таким образом, коллекция не обязательно подразумевает числовую индексацию.

Другая причина в том, что IEnumerable на самом деле не двухсторонний трафик. Подумайте об этом так: IEnumerable может перечислять элементы x раз, как вы указываете, и находить элемент в x (то есть ElementAt), но он не может точно знать, находится ли какой-либо из его элементов в каком индексе (то есть IndexOf) .

Но да, это все равно довольно странно, даже если вы так думаете, поскольку ожидаете, что у него будет либо одновременно ElementAt и IndexOf , либо ничего.

person Ian    schedule 25.05.2016

IndexOf - это метод, определенный в List, тогда как IReadOnlyCollection наследует только IEnumerable.

Это потому, что IEnumerable предназначен только для итерации сущностей. Однако индекс не применяется к этой концепции, потому что порядок произвольный и не гарантируется идентичность между вызовами IEnumerable. Кроме того, интерфейс просто заявляет, что вы можете перебирать коллекцию, тогда как List заявляет, что вы также можете выполнять добавление и удаление.

Метод ElementAt делает именно это. Однако я не буду использовать его, поскольку он повторяет все перечисление, чтобы найти один единственный элемент. Лучше использовать First или просто подход на основе списков.

В любом случае дизайн API кажется мне странным, поскольку он допускает (неэффективный) подход к получению элемента в n -ой позиции, но не позволяет получить индекс произвольного элемента, который был бы таким же неэффективным поиск до n итераций. Я согласен с Яном либо в обоих (что я бы не рекомендовал), либо ни в одном из них.

person HimBromBeere    schedule 25.05.2016
comment
Я полностью согласен с вами в том, что Enumerables не предназначены для доступа по индексу, но все же я могу использовать ElementAt (). Для меня это немного глупо, я получаю элемент в заданной позиции, но не позицию элемента. Это скорее философский вопрос :) - person Lorenzo Santoro; 25.05.2016
comment
Это действительно странный метод. Я предполагаю, что он существует просто потому, что кому-то нужно это будущее, однако до сих пор не было большого демада к IndexOf-методу. - person HimBromBeere; 25.05.2016

IReadOnlyCollection<T> имеет ElementAt<T>(), потому что это расширение IEnumerable<T>, в котором есть этот метод. ElementAt<T>() выполняет IEnumerable<T> заданное количество итераций и возвращает значение в качестве этой позиции.

IReadOnlyCollection<T> не хватает IndexOf<T>(), потому что, как IEnumerable<T>, он не имеет какого-либо определенного порядка, и поэтому концепция индекса не применяется. И IReadOnlyCollection<T> не добавляет никакой концепции порядка.

Я бы порекомендовал IReadOnlyList<T>, если вам нужна индексируемая версия IReadOnlyCollection<T>. Это позволяет правильно представить неизменяемую коллекцию объектов с индексом.

person Licht    schedule 29.01.2019
comment
Не используйте это на самом деле не ответ, но в том, что вы говорите, есть некоторая ценность. Возможно, вы могли бы сделать это больше похожим на ответ, указав, что ICollection<T> также не имеет IndexOf() метода, тогда как IList<T> имеет. Так что это просто вопрос семантики, то есть коллекция рассматривается как мешок без порядка (вероятно, но это всегда проблема с этими вопросами почему: кто знает?). - person Gert Arnold; 29.01.2019
comment
@GertArnold Ты прав. Я полагался на пользователя, который читал мой комментарий последним. Теперь, когда вы указали на это, я вижу в этом проблему. Отредактирую свой ответ, чтобы он был полнее. - person Licht; 29.01.2019
comment
Основная проблема в том, что IReadOnlyList<T> также не имеет реализованной функции IndexOf(). IReadOnlyCollection не иметь его - не такая уж большая проблема, поскольку он не имеет семантики индексации, но весь смысл использования списка заключается в том, что он должен быть упорядоченным и индексируемым. У List<T> и IList<T> он есть, а у IReadOnlyList его нет. Это довольно неприятно. - person dsmith; 12.07.2019

Это может быть кому-то полезно:

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;
}
person George Tachev    schedule 10.07.2020
comment
Привет, добро пожаловать в StackOverflow! Спасибо за участие. Пожалуйста, ознакомьтесь с ответом Майка Накиса и подумайте, не является ли ваш ответ дубликатом. Если нет, отредактируйте свой ответ и предоставьте небольшое дополнительное объяснение и пример кода, вызывающего вашу функцию, особенно в отношении параметра предиката. Спасибо! - person fose; 10.07.2020
comment
Он почти такой же, как у Майка. Единственная разница в том, что он использует предикат, поэтому вы можете использовать его так: var index = list.IndexOf (obj = ›obj.Id == id) - person George Tachev; 14.07.2020