Какой смысл Enumerable.ElementAt <TSource>?

IEnumerable<T> выдает перечислитель, поэтому объект можно перечислить. Нет ничего об индексах, открытых этим интерфейсом. IList<T> относится к индексам, поскольку предоставляет метод IndexOf.

Итак, какова точка Enumerable.ElementAt? Я просто прочитал doc этого метода расширения LINQ:

Возвращает элемент по указанному индексу в последовательности.

Ну, да, это примерно последовательность, а не только IEnumerable. Чтение замечаний:

Если тип источника реализует IList, эта реализация используется для получения элемента по указанному индексу. В противном случае этот метод получает указанный элемент.

Хорошо, поэтому, если конкретный тип реализует что-то, что наследует от IList<T> (которое является фактической последовательностью), то оно равно, как IndexOf(). Если нет, он выполняет итерацию до достижения индекса.

Вот пример сценария:

// Some extension method exposed by a lib
// I know it not a good piece of code, but let say it coded this way:
public static class EnumerableExtensions
{
    // Returns true if all elements are ordered
    public static bool IsEnumerableOrdered(this IEnumerable<int> value)
    {
        // Iterates over elements using an index
        for (int i = 0; i < value.Count() - 1; i++)
        {
            if (value.ElementAt(i) > value.ElementAt(i + 1))
            {
                return false;
            }
        }

        return true;
    }
}

// Here a collection that is enumerable, but doesn't always returns
// its objects in the same order
public class RandomAccessEnumerable<T> : IEnumerable<T>
{
    private List<T> innerList;
    private static Random rnd = new Random();

    public RandomAccessEnumerable(IEnumerable<T> list)
    {
        innerList = list.ToList();
    }

    public IEnumerator<T> GetEnumerator()
    {
        var listCount = this.innerList.Count;
        List<int> enumeratedIndexes = new List<int>();

        for (int i = 0; i < listCount; i++)
        {
            int randomIndex = -1;
            while (randomIndex < 0 || enumeratedIndexes.Contains(randomIndex))
            {
                randomIndex = rnd.Next(listCount);
            }

            enumeratedIndexes.Add(randomIndex);
            yield return this.innerList[randomIndex];
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

// Here some test program
internal class Program
{
    private static void Main()
    {
        var test0 = new List<int> { 0, 1, 2, 3 };
        var test1 = new RandomAccessEnumerable<int>(test0);

        Console.WriteLine("With List");
        Console.WriteLine(test0.IsEnumerableOrdered()); // true
        Console.WriteLine(test0.IsEnumerableOrdered()); // true
        Console.WriteLine(test0.IsEnumerableOrdered()); // true
        Console.WriteLine(test0.IsEnumerableOrdered()); // true
        Console.WriteLine(test0.IsEnumerableOrdered()); // true

        Console.WriteLine("With RandomAccessEnumerable");
        Console.WriteLine(test1.IsEnumerableOrdered()); // might be true or false
        Console.WriteLine(test1.IsEnumerableOrdered()); // might be true or false
        Console.WriteLine(test1.IsEnumerableOrdered()); // might be true or false
        Console.WriteLine(test1.IsEnumerableOrdered()); // might be true or false
        Console.WriteLine(test1.IsEnumerableOrdered()); // might be true or false

        Console.Read();
    }
}

Итак, поскольку RandomAccessEnumerable может возвращать перечисленные объекты в случайном порядке, вы просто не можете полагаться на простой интерфейс IEnumerable<T>, чтобы предположить, что ваши элементы индексированы. Поэтому вы не хотите использовать ElementAt для IEnumerable.

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

Ответ 1

Существует много типов IEnumerable, таких как массив или список. Все типы IList (которые Array также реализуют) имеют indexer, который можно использовать для доступа к элементам с определенным индексом.

Это будет использоваться Enumerable.ElementAt, если последовательность может быть выполнена на IList успешно. В противном случае он будет указан.

Таким образом, это просто удобный способ доступа к элементам в данном индексе для всех типов IEnumerable.

Это имеет то преимущество, что вы можете изменить тип позже, не изменяя все вхождения arr[index].

Для чего это стоит, здесь отраженный (ILSpy) метод, чтобы продемонстрировать, что я сказал:

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    IList<TSource> list = source as IList<TSource>;
    if (list != null)
    {
        return list[index];
    }
    if (index < 0)
    {
        throw Error.ArgumentOutOfRange("index");
    }
    TSource current;
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            if (index == 0)
            {
                current = enumerator.Current;
                return current;
            }
            index--;
        }
        throw Error.ArgumentOutOfRange("index");
    }
    return current;
}