Я нашел этот метод:
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()
один раз и сохранять результат в переменной?
@HansPassant не пришлось бы First()
создавать конечный автомат/то, что участвует в перечислении IEnumerable<T>
? Или он достаточно умен, чтобы знать, что это List<T>
и он просто возвращает первый элемент. Это происходит return members[0]
за кулисами в First()
?
@DavidKlempfner — вы можете проверить исходный код здесь. Текущая версия проверяет наличие source is IList<TSource> list)
и обращается к списку по индексу. Однако присвоение первого значения переменной, а не получение ее дважды, по-прежнему кажется лучшей практикой.
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)
, если вам действительно нужно повторить его несколько раз.
Шансы на то, что вы увидите какую-либо разницу в выполнении задачи, практически равны нулю. Ну нет. Хорошая особенность переменных заключается в том, что код гораздо проще отлаживать.