Другое

LINQ First() vs FirstOrDefault(): Когда использовать в LINQ?

Полное руководство по выбору между LINQ First() и FirstOrDefault(). Узнайте, когда элементы должны генерировать исключения, а когда возвращать значения по умолчанию.

Когда следует использовать .First() вместо .FirstOrDefault() в LINQ?

Я искал информацию, но не нашёл чёткого ответа, когда стоит использовать .First(), а когда .FirstOrDefault() в LINQ.

  • Когда стоит использовать .First()? Только когда нужно поймать исключение, если результатов нет?

    csharp
    var result = List.Where(x => x == "foo").First();
    
  • А когда стоит использовать .FirstOrDefault()? Когда всегда хочется получить значение по умолчанию, если результата нет?

    csharp
    var result = List.Where(x => x == "foo").FirstOrDefault();
    
  • И кстати, как насчёт Take()?

    csharp
    var result = List.Where(x => x == "foo").Take(1);
    

Используйте .First(), когда вы ожидаете хотя бы один элемент, а пустая последовательность должна рассматриваться как исключительная ситуация, требующая выброса исключения.
Используйте .FirstOrDefault(), когда нулевое количество совпадений допустимо и является частью нормального потока программы, позволяя вам корректно обрабатывать отсутствие элементов.
Рассмотрите Take(1), когда вам нужна последовательность, а не один элемент, или при работе с Entity Framework, где перевод запроса может отличаться.

Содержание

Основные различия между First и FirstOrDefault

Ключевое различие между First() и FirstOrDefault() заключается в том, как они обрабатывают отсутствие подходящих элементов. Согласно Stack Overflow, First() выбрасывает исключение, если нет элементов, удовлетворяющих условию, тогда как FirstOrDefault() возвращает значение по умолчанию для типа данных.

csharp
// Это вызовет InvalidOperationException, если "foo" не найден
var result = List.Where(x => x == "foo").First();

// Это вернёт null, если "foo" не найден (для ссылочных типов)
var result = List.Where(x => x == "foo").FirstOrDefault();

Значение по умолчанию зависит от типа данных:

  • Ссылочные типы: null
  • Значимые типы: 0 для int, false для bool и т.д.
  • Nullable типы: null для int?, string? и т.д.

Как объясняет DEV Community, «Основное различие между First и FirstOrDefault заключается в том, что First() выбрасывает исключение, если нет результата для заданных критериев, тогда как FirstOrDefault() возвращает значение по умолчанию (null), если результата нет».

Когда использовать First()

Используйте First() в сценариях, где пустая последовательность представляет собой исключительную ситуацию, которую не должно происходить в нормальном выполнении программы. Согласно Reddit, «First() предназначен для случаев, когда пустой IEnumerable является исключительным состоянием в вашей программе и не должен происходить».

Подходящие сценарии для First():

  • Запросы к базе данных, где вы ожидаете ровно одну запись
  • Настройки конфигурации, которые должны присутствовать
  • Правила бизнеса, гарантирующие наличие хотя бы одного подходящего элемента
  • Когда отсутствие данных указывает на критическую ошибку, требующую немедленного внимания
csharp
// Пример: Получение активной сессии пользователя – должна всегда существовать
var activeSession = userSessions
    .Where(s => s.UserId == currentUserId && s.IsActive)
    .First(); // Выбрасывает исключение, если активной сессии нет

// Пример: Загрузка настроек приложения – должны существовать
var appSettings = configuration
    .GetSection("ApplicationSettings")
    .First(); // Критично, если отсутствует

Выбрасываемое First() исключение – InvalidOperationException с сообщением «Sequence contains no elements», что чётко сообщает разработчикам и инструментам отладки о проблеме.

Когда использовать FirstOrDefault()

Используйте FirstOrDefault(), когда нулевое количество совпадений допустимо и является частью нормального потока выполнения. Согласно Dot Net Tutorials, «если вы хотите получить значение по умолчанию в зависимости от типа данных, вам нужно использовать метод LINQ FirstOrDefault».

Подходящие сценарии для FirstOrDefault():

  • Поиск пользовательского ввода, который может отсутствовать в коллекции
  • Опциональные значения конфигурации
  • Поиск записей в базе данных, которые могут отсутствовать
  • Когда необходимо проверить наличие перед дальнейшей обработкой
csharp
// Пример: Поиск пользователя по имени пользователя – может не существовать
var user = users.FirstOrDefault(u => u.Username == inputUsername);
if (user != null)
{
    // Пользователь найден, продолжаем логику
}
else
{
    // Пользователь не найден, обрабатываем без ошибок
}

// Пример: Получение первой непрочитанной важной уведомления, если таковое есть
var notification = notifications
    .FirstOrDefault(n => n.IsUnread && n.IsImportant);
if (notification != null)
{
    notification.MarkAsRead();
}

Как отмечает NashTech Blog, «FirstOrDefault(): Этот метод возвращает первый элемент последовательности, удовлетворяющий заданному условию, или значение по умолчанию, если такой элемент не найден. Значение по умолчанию для ссылочных типов – null, а для значимых типов – соответствующее значение…».

Проблемы производительности

Оба метода First() и FirstOrDefault() выполняют линейный поиск по коллекциям с сложностью O(n). Согласно Stack Overflow, «FirstOrDefault выполняет стандартный линейный поиск по исходной коллекции и возвращает первый элемент, удовлетворяющий предикату. Это O(n), поэтому не удивительно, что он занимает больше времени на больших коллекциях».

Ключевые выводы по производительности:

  • LINQ to Objects: оба метода имеют схожие характеристики производительности
  • Entity Framework: перевод запроса может влиять на производительность
  • Большие коллекции: рассмотрите альтернативные подходы для очень больших наборов данных
csharp
// В LINQ to Objects оба метода итерируют до первого совпадения
var result1 = largeList.First(x => x.Id == 100);  // O(n)
var result2 = largeList.FirstOrDefault(x => x.Id == 100); // O(n)

// Разница в производительности минимальна – накладные расходы на обработку исключения
// незначительны по сравнению с затратами на итерацию

Для Entity Framework особенности производительности различаются. Как отмечено в Stack Overflow, «First будет выполнять запрос Take 1, так что разницы в запросе нет. Вызывая FirstOrDefault, вы получите один шаг, потому что Take возвращает IEnumerable, вам всё равно понадобится вызвать First».

Сравнение Take() и First/FirstOrDefault

Take(1) существенно отличается от First() и FirstOrDefault() по типу возвращаемого значения и поведению. Согласно Stack Overflow, «First() возвращает сам элемент, тогда как Take(1) возвращает последовательность, содержащую ровно один элемент».

Ключевые различия:

Метод Тип возвращаемого значения Поведение при пустой последовательности Сценарий использования
First() Один элемент Выбрасывает InvalidOperationException Когда нужен элемент, а пустота – исключение
FirstOrDefault() Один элемент Возвращает значение по умолчанию Когда пустота допустима и нужно её обработать
Take(1) IEnumerable<T> Возвращает пустую последовательность Когда нужна последовательность, а не один элемент
csharp
// Take(1) возвращает коллекцию, а не один элемент
var results = users.Where(u => u.IsActive).Take(1);
// results – IEnumerable<User>, а не User

// Всё равно нужно вызвать First() или FirstOrDefault() на результате
var firstActiveUser = users.Where(u => u.IsActive).Take(1).First();

// Это менее эффективно, чем просто использовать First() напрямую
var betterApproach = users.Where(u => u.IsActive).First();

Как упоминается в Reddit discussion, «FirstOrDefault() – один из самых (пер)используемых расширений IEnumerable». В статье предлагается, что Take(1) может быть семантически отличным, но часто First() или FirstOrDefault() более подходящими, когда нужен один элемент.

Лучшие практики и примеры кода

Принцип руководства: выбирайте метод в зависимости от того, ожидается ли пустая последовательность или она считается исключением.

Антипаттерны, которых следует избегать

csharp
// ❌ Не используйте FirstOrDefault(), когда пустота должна быть исключением
var criticalSetting = config.FirstOrDefault(c => c.Key == "CriticalSetting");
if (criticalSetting == null)
{
    // Это скрывает критическую ошибку
    throw new ConfigurationException("Critical setting missing");
}

// ✅ Лучше использовать First() для исключительных случаев
var criticalSetting = config.First(c => c.Key == "CriticalSetting");
// Позвольте исключению всплыть естественным образом

Паттерны оптимизации производительности

csharp
// Для больших коллекций рассмотрите Any() + First() для лучшей читаемости
var largeCollection = GetLargeCollection();
if (largeCollection.Any(x => x.Id == targetId))
{
    var result = largeCollection.First(x => x.Id == targetId);
    // Используйте результат
}
else
{
    // Обрабатываем случай, когда не найдено
}

// Для Entity Framework оба First() и Take(1) генерируют похожий SQL
var efResult = dbContext.Users
    .Where(u => u.IsActive)
    .First(); // Генерирует TOP 1 в SQL

var efTakeResult = dbContext.Users
    .Where(u => u.IsActive)
    .Take(1)
    .First(); // Также генерирует TOP 1 в SQL

Асинхронные варианты

csharp
// Асинхронные версии следуют тем же принципам
var user = await users.FirstOrDefaultAsync(u => u.Username == username);
if (user != null)
{
    // Обрабатываем найденный случай
}
else
{
    // Обрабатываем отсутствие без ошибок
}

// Используйте FirstAsync, когда пустота исключительна
var session = await sessions.FirstAsync(s => s.UserId == userId && s.IsActive);

Как отмечает Raphael Anyanwu, «Понимание различий между этими методами LINQ и их асинхронными аналогами критично для написания эффективного кода C#».

Источники

  1. Stack Overflow – Когда использовать .First и когда использовать .FirstOrDefault с LINQ?
  2. DEV Community – C# First() или FirstOrDefault()
  3. Dot Net Tutorials – LINQ First и FirstOrDefault Method in C#
  4. Medium – First vs FirstOrDefault, Single vs SingleOrDefault: A High-level Look
  5. NashTech Blog – LINQ First and FirstOrDefault Methods in C# Explained
  6. Reddit – FirstOrDefault or First, SingleOrDefault or Single
  7. Stack Overflow – C# Linq to Objects – FirstOrDefault performance
  8. Stack Overflow – With Entity Framework is it better to use .First() or .Take(1) for “TOP 1”?

Вывод

Выбор между First() и FirstOrDefault() сводится к ожиданиям вашей программы и стратегии обработки ошибок. Используйте First(), когда пустая последовательность представляет критическую ошибку, которую не должно происходить в нормальном ходе работы. Используйте FirstOrDefault(), когда нулевое количество совпадений допустимо и является частью бизнес-логики. Помните, что Take(1) возвращает последовательность, а не один элемент, что делает его подходящим для других сценариев.

Ключевые выводы:

  • First() выбрасывает исключения при пустой последовательности – используйте для исключительных условий
  • FirstOrDefault() возвращает значение по умолчанию при пустой последовательности – используйте для допустимых случаев
  • Разница в производительности минимальна, за исключением крайних случаев с Entity Framework
  • Учитывайте читаемость и поддерживаемость вместе с производительностью
  • Документируйте свои предположения о доступности данных в комментариях к коду

Понимая эти нюансы, вы сможете писать более надёжный, поддерживаемый код, который чётко выражает свои ожидания и корректно обрабатывает крайние случаи.

Авторы
Проверено модерацией
Модерация