Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 303
ken2k Avatar asked Jan 15 '13 13:01

ken2k


People also ask

What is ElementAt C#?

ElementAt() is a System. Linq method in C# that is used to get and display element at a particular index. The following is our string array − string[] arr = { "One", "Two", "Three", "Four", "Five" }; Now to get an element at index 0, use the ElementAt() method − arr.ElementAt(0); The following is the complete code −

What is Linq enumerable?

IEnumerable interface is a generic interface which allows looping over generic or non-generic lists. IEnumerable interface also works with linq query expression. IEnumerable interface Returns an enumerator that iterates through the collection.

Do lists have indexes C#?

The IndexOf method returns the first index of an item if found in the List. C# List<T> class provides methods and properties to create a list of objects (classes). The IndexOf method returns the first index of an item if found in the List.


1 Answers

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;
}
like image 143
Tim Schmelter Avatar answered Oct 22 '22 18:10

Tim Schmelter