Плохо ли вызывать First() несколько раз для IEnumerable<T>, который на самом деле является List<T>?

Я нашел этот метод:

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

Это называется так:

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

Я знаю, что это может быть особенно плохо, если GetMembers() вернет IQueryable<Member>, но, поскольку это всего лишь List<Member>, разве плохо вызывать First() дважды таким образом? Лучше всего вызывать First() один раз и сохранять результат в переменной?

Шансы на то, что вы увидите какую-либо разницу в выполнении задачи, практически равны нулю. Ну нет. Хорошая особенность переменных заключается в том, что код гораздо проще отлаживать.

Hans Passant 24.04.2024 02:39

@HansPassant не пришлось бы First() создавать конечный автомат/то, что участвует в перечислении IEnumerable<T>? Или он достаточно умен, чтобы знать, что это List<T> и он просто возвращает первый элемент. Это происходит return members[0] за кулисами в First()?

David Klempfner 24.04.2024 02:42

@DavidKlempfner — вы можете проверить исходный код здесь. Текущая версия проверяет наличие source is IList<TSource> list) и обращается к списку по индексу. Однако присвоение первого значения переменной, а не получение ее дважды, по-прежнему кажется лучшей практикой.

dbc 24.04.2024 03:13
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
115
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

First функция в System.Linq.Enumerable

В System.Linq.Enumerable вот как была реализована функция First:

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

    return first!;
}

TryGetFirst позвонили, посмотрим:

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

Потом позвонили TryGetFirstNonIterator, давайте посмотрим:

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

Как мы видим, он возвращает list[0], если источником является IList<TSource>. Исходный код

Предложение SomeMethod

Независимо от того, как был реализован First. Вопрос, который вы задали:

Я знаю, что это может быть особенно плохо, если GetMembers() вернет IQueryable, но, поскольку это всего лишь список, плохо ли вызывать First() дважды таким образом? Лучше всего вызывать First() один раз и сохранять результат в переменной?

Я думаю, что перегрузка — это все, что вам нужно.

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

Лучше всего вызывать First() один раз и сохранять результат в переменной?

Да, потому что вы не знаете, как этот метод будет использоваться. Даже если сегодня он используется только со списком, завтра кто-то может сделать что-то еще. Например, вызывая это примерно так:

public IEnumerable<Member> GetMembers(){
    var someValue = VeryExpensiveFunction();
    yield return new Member(someValue);
}

В этом случае каждый вызов .First() должен будет выполняться VeryExpensiveFunction().

Хорошим решением является сохранение результата в переменной, или вы можете объявить метод как SomeMethod(List<Member> members) или SomeMethod(IReadOnlyList<Member> members), если вам действительно нужно повторить его несколько раз.

Другие вопросы по теме