Search code examples
c#ienumerable.net-7.0iqueryable

Is it bad to call First() multiple times on an IEnumerable<T> that is actually a List<T>?


I found this method:

public async Task SomeMethod(IEnumerable<Member> members)
{
    await DoSomething(members.First());
    await DoSomethingElse(members.First());
}

It's being called like this:

List<Member> members = GetMembers();
await SomeMethod(members);

I know this could be particularly bad if GetMembers() returned an IQueryable<Member>, but since it's just a List<Member>, is it still bad to call First() twice like that? Is it best practice to call First() once and store the result in a variable?


Solution

  • First function in System.Linq.Enumerable

    In System.Linq.Enumerable here's how First function was implemented:

    public static TSource First<TSource>(this IEnumerable<TSource> source)
    {
        TSource? first = source.TryGetFirst(out bool found);
        if (!found)
        {
            ThrowHelper.ThrowNoElementsException();
        }
    
        return first!;
    }
    

    TryGetFirst was called, let's have a look:

    private static TSource? TryGetFirst<TSource>(this IEnumerable<TSource> source, out bool found)
    {
        if (source is null)
        {
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
        }
    
        return
    #if !OPTIMIZE_FOR_SIZE
            source is Iterator<TSource> iterator ? iterator.TryGetFirst(out found) :
    #endif
            TryGetFirstNonIterator(source, out found);
    }
    

    Then TryGetFirstNonIterator was called, let's have a look:

    private static TSource? TryGetFirstNonIterator<TSource>(IEnumerable<TSource> source, out bool found)
    {
        if (source is IList<TSource> list)
        {
            if (list.Count > 0)
            {
                found = true;
                return list[0];
            }
        }
        else
        {
            using (IEnumerator<TSource> e = source.GetEnumerator())
            {
                if (e.MoveNext())
                {
                    found = true;
                    return e.Current;
                }
            }
        }
    
        found = false;
        return default;
    }
    

    As we can see, it does return list[0] if the soruce is IList<TSource>. Source Code

    Suggestion to SomeMethod

    Regardless of how First was implemented. The question you asked:

    I know this could be particularly bad if GetMembers() returned an IQueryable, but since it's just a List, is it still bad to call First() twice like that? Is it best practice to call First() once and store the result in a variable?

    I think overloading is all you need.

    public async Task SomeMethod(Member member)
    {
        await DoSomething(member);
        await DoSomethingElse(member);
    }
    public async Task SomeMethod(IEnumerable<Member> members)
    {
        await SomeMethod(members.First());
    }