What's the point of Enumerable.ElementAt<TSource>?

青春壹個敷衍的年華 提交于 2019-12-23 09:28:03

问题


IEnumerable<T> exposes an enumerator, so the object can be enumerated. There is nothing about indexes exposed by this interface. IList<T> is about indexes, as it exposes the IndexOf method.

So what's the point of Enumerable.ElementAt? I just read the doc of this LINQ extension method:

Returns the element at a specified index in a sequence.

Well, yes, it's about a sequence, not just an IEnumerable. Reading the remarks:

If the type of source implements IList, that implementation is used to obtain the element at the specified index. Otherwise, this method obtains the specified element.

Okay, so if the concrete type implements something that inherits from IList<T> (which is an actual sequence), then it's the same as IndexOf(). If not, it iterates until the index is reached.

Here's a sample scenario:

// Some extension method exposed by a lib
// I know it's not a good piece of code, but let's say it's 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's 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's 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();
    }
}

So, as RandomAccessEnumerable might return enumerated objects in a random order, you just can't rely on the simple IEnumerable<T> interface to assume your elements are indexed. So you don't want to use ElementAt for an IEnumerable.

In the above example, I think IsEnumerableOrdered should require a IList<T> parameter as it implies elements are a sequence. I actually can't find a scenario where the ElementAt method is useful, and not bug-prone.


回答1:


There are many IEnumerable types like array or list. All IList types(which Array also implements) have an indexer which you can use to access elements at a specific index.

This will be used by Enumerable.ElementAt if the sequence can be casted to IList successfully. Otherwise it will be enumerated.

So it's just a convenient way to access elements at a given index for all kind of IEnumerable types.

This has the advantage that you can change the type later without needing to change all occurences of arr[index].

For what it's worth, here's the reflected(ILSpy) method to demonstrate what i've said:

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;
}


来源:https://stackoverflow.com/questions/14338680/whats-the-point-of-enumerable-elementattsource

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!