Почему я должен использовать Enumerable.ElementAt() по сравнению с оператором []?

Это кажется глупым вопросом, но я не нашел ответа, так что вот оно.:)

В обоих случаях вы получите исключение "вне диапазона", если не сможете проверить границы своей коллекции. Это только предпочтение стиля кодирования?

И в случае, если кому-то нужен пример:

List<byte> myList = new List<byte>(){0x01, 0x02, 0x03};
byte testByte = myList.ElementAt(2);

против

byte testByte = myList[2];

Ответ 1

Поскольку Enumerable является более общим, а коллекция, представленная перечислимым, может не иметь индексатора.

Но если это так - не используйте ElementAt(), вероятно, это будет не так эффективно.

Ответ 2

ElementAt() предоставляет унифицированный интерфейс для всех перечислений на С#. Я часто использую его довольно часто, поскольку мне нравится общий API.

Если базовый тип поддерживает произвольный доступ (т.е. поддерживает оператор []), то ElementAt будет использовать это. Таким образом, только накладные расходы - это дополнительный вызов метода (что почти никогда не актуально).

Если базовый тип не поддерживает произвольный доступ, то ElementAt() имитирует его, итерируя перечисление до тех пор, пока он не достигнет элемента, который вас интересует. Это может быть очень дорого и иногда иметь побочные эффекты.

Существует также ElementAtOrDefault(), который часто очень удобен.

Ответ 3

Обновление. Я чувствовал, что это интересная тема, и решил сообщить об этом.

В принципе, я считаю, что редко бывает целесообразно использовать абстракцию для случайного доступа, не требуя IList<T>. Если вы пишете код, который хочет получить доступ к элементам коллекции случайным образом по индексу, то просто требуется тот, который дает вам это. И если вам требуется IList<T> для начала, я не вижу смысла использовать ElementAt вместо индексатора.

Но это просто мое мнение, конечно.


Не используйте его для реализаций IList<T> типа T[] или List<T>. Используйте его только в том случае, если вам нужны типы сбора, которые не предоставляют произвольный доступ, например Queue<T> или Stack<T>.

var q = new Queue<int>();
var s = new Stack<int>();
for (int i = 0; i < 10; ++i)
{
    q.Enqueue(i);
    s.Push(i);
}

// q[1] would not compile.
int fromQueue = q.ElementAt(1);
int fromStack = s.ElementAt(1);

Ответ 4

ИЗБЕГАЙТЕ использование ElementAt() в определенных сценариях!!!

Если вы знаете, что собираетесь искать каждый элемент, и у вас есть (или может быть) более 500, тогда просто вызывайте ToArray(), храните его в переменной многократного использования и индексируйте ее таким образом.

Например; мой код читал данные из файла Excel.
Я использовал ElementAt(), чтобы найти SharedStringItem, на который ссылалась моя ячейка.
С 500 или менее строками вы, вероятно, не заметите разницы.
С линиями 16K это заняло 100 секунд.

Чтобы все ухудшилось (с каждой прочитанной строкой), чем больше индекс вырос, тем больше он должен был индексироваться на каждой итерации, поэтому потребовалось больше времени, чем нужно.
Первые 1000 строк заняли 2,5 секунды, а последние 1000 строк заняли 10,7 секунды.

Прохождение через эту строку кода:

SharedStringItem ssi = sst.Elements<SharedStringItem>().ElementAt(ssi_index);

Результат в этом журнале:

...Using ElementAt():
RowIndex:  1000 Read: 1,000 Seconds: 2.4627589
RowIndex:  2000 Read: 1,000 Seconds: 2.9460492
RowIndex:  3000 Read: 1,000 Seconds: 3.1014865
RowIndex:  4000 Read: 1,000 Seconds: 3.76619
RowIndex:  5000 Read: 1,000 Seconds: 4.2489844
RowIndex:  6000 Read: 1,000 Seconds: 4.7678506
RowIndex:  7000 Read: 1,000 Seconds: 5.3871863
RowIndex:  8000 Read: 1,000 Seconds: 5.7997721
RowIndex:  9000 Read: 1,000 Seconds: 6.4447562
RowIndex: 10000 Read: 1,000 Seconds: 6.8978011
RowIndex: 11000 Read: 1,000 Seconds: 7.4564455
RowIndex: 12000 Read: 1,000 Seconds: 8.2510054
RowIndex: 13000 Read: 1,000 Seconds: 8.5758217
RowIndex: 14000 Read: 1,000 Seconds: 9.2953823
RowIndex: 15000 Read: 1,000 Seconds: 10.0159931
RowIndex: 16000 Read: 1,000 Seconds: 10.6884988
Total Seconds: 100.6736451

Как только я создал промежуточный массив для хранения SharedStringItem для ссылок, мое время прошло 100 секунд до 10 секунд и теперь обрабатывает каждую строку за тот же промежуток времени.

Эта строка кода:

SharedStringItem[] ssia = sst == null ? null : sst.Elements<SharedStringItem>().ToArray();
Console.WriteLine("ToArray():" + watch.Elapsed.TotalSeconds + " Len:" + ssia.LongCount());

и Цикл через эту строку кода:

SharedStringItem ssi = ssia[ssi_index];

Результат в этом журнале:

...Using Array[]:
ToArray(): 0.0840583 Len: 33560
RowIndex:  1000 Read: 1,000 Seconds: 0.8057094
RowIndex:  2000 Read: 1,000 Seconds: 0.8183683
RowIndex:  3000 Read: 1,000 Seconds: 0.6809131
RowIndex:  4000 Read: 1,000 Seconds: 0.6530671
RowIndex:  5000 Read: 1,000 Seconds: 0.6086124
RowIndex:  6000 Read: 1,000 Seconds: 0.6232579
RowIndex:  7000 Read: 1,000 Seconds: 0.6369397
RowIndex:  8000 Read: 1,000 Seconds: 0.629919
RowIndex:  9000 Read: 1,000 Seconds: 0.633328
RowIndex: 10000 Read: 1,000 Seconds: 0.6356769
RowIndex: 11000 Read: 1,000 Seconds: 0.663076
RowIndex: 12000 Read: 1,000 Seconds: 0.6633178
RowIndex: 13000 Read: 1,000 Seconds: 0.6580743
RowIndex: 14000 Read: 1,000 Seconds: 0.6518182
RowIndex: 15000 Read: 1,000 Seconds: 0.6662199
RowIndex: 16000 Read: 1,000 Seconds: 0.6360254
Total Seconds: 10.7586264

Как вы можете видеть, преобразование в массив потребовало доли секунды для 33 560 элементов и стоило того, чтобы ускорить процесс импорта.

Ответ 5

ElementAt() пытается сначала использовать IList<T>, а затем использовать индексатор, поэтому производительность с ElementAt(), вероятно, не будет существенно отличаться от использования индексатора напрямую. Поэтому, даже если у вас есть IList<T>, вы можете рассмотреть ElementAt(), если есть вероятность, что вы измените свой объявленный тип в будущем.

Ответ 6

С семантической точки зрения, оба они делают то же самое. Какой бы вы ни использовали его, чтобы не менять смысл программы.

Вы используете []:

  • если перечисляемый объявлен как IList<T>, потому что:

    a. он более читабельный, идиоматический и широко понимаемый. Нет 1 причины для меня использовать его.

    b. вы хотите оптимизировать каждый дюйм. [] должен быть немного более результативным, так как он смотрит прямо вверх и избегает приведения (обратите внимание, что ElementAt использует индекс, если IEnumerable<T> является IList<T>, следовательно, это не большой коэффициент усиления).

    c., вы все еще в эпоху .NET 3.5.

Вы используете ElementAt:

  • если объявленный тип является просто IEnumerable<T>.

  • если ElementAt делает его более читаемым. Например, при беглых вызовах стиля:

    var x = GetThatList().ElementAt(1).Wash().Spin().Rinse().Dry();
    
    //is more readable than
    
    var x = GetThatList()[1].Wash().Spin().Rinse().Dry();
    
  • если вы действительно непреклонны в использовании согласованного стиля для IList<T> и IEnumerable<T>.

Простыми словами, если у вас есть переменная, объявленная как IList<T>, используйте [], в противном случае используйте ElementAt (я подчеркиваю, что точка "объявлена", потому что, даже если ваш IEnumerable<T> является IList<T> внизу, вы может использовать только ElementAt. Приведение к IList<T> для использования индексатора - это нет нет (это происходит внутри ElementAt в любом случае)). Считываемость торжествует. Если у вас есть это в виду, его просто выбрать. Стиль Indexer - это то, что я должен использовать в подавляющем большинстве случаев. Я вынужден использовать ElementAt очень редко.

Ответ 7

IEnumerable не поддерживает прямой доступ к индексу, если вы знаете, что у вас есть список, продолжайте использовать вторую форму, поскольку ее гораздо легче читать (возможно).

ElementAt() поддерживается для всех перечислений, а не только для списков - их производительность одинакова при использовании в типе IList (т.е. в общем списке), но если у вас есть список, использующий индексный доступ, это больше выразительный. Если вы посмотрите на источник ElementAt() с отражателем, вы увидите, что он будет внутренне использовать доступ к индексу, если IEnumerable имеет тип IList:

..
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
    return list[index];
}
..

Ответ 8

Единственная причина использования ElementAt() over [] - это то, что вам нужно или нужно IEnumerable вместо IList.