Start Debugging

Решение: System.InvalidOperationException: Sequence contains no elements

Это исключение означает, что вы вызвали .First() или .Single() на пустой последовательности. Используйте FirstOrDefault/SingleOrDefault с проверкой на null, защитите запрос или устраните причину пустоты источника.

System.InvalidOperationException: Sequence contains no elements означает, что вы вызвали .First(), .Single(), .Last() или один из их агрегирующих родственников (.Average(), .Max(), .Min()) на последовательности, которая оказалась пустой. Оператор обещал вернуть элемент, а его не было, поэтому он выбросил исключение. Решение состоит в том, чтобы определить, что “пусто” должно означать для этого вызова: если пустота является нормальным исходом, переключитесь на .FirstOrDefault() / .SingleOrDefault() и обработайте null (или значение по умолчанию), которое вы получаете обратно; если пустота является ошибкой, исправьте запрос или данные, чтобы последовательность никогда не была пустой в этой точке. Это проверено на .NET 11, C# 14 и EF Core 11.0.0, но сообщение и поведение остаются стабильными с тех пор, как LINQ появился в .NET Framework 3.5, поэтому руководство применимо к любой версии.

Ошибка в контексте

Полное исключение, выброшенное изнутри System.Linq, выглядит так:

System.InvalidOperationException: Sequence contains no elements
   at System.Linq.ThrowHelper.ThrowNoElementsException()
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
   at MyApp.OrderService.GetLatest() in /src/OrderService.cs:line 42

Подсказка находится в верхнем кадре: System.Linq.ThrowHelper.ThrowNoElementsException. Если вы видите это в трассировке стека, значит, возвращающий элемент оператор LINQ отработал на пустом источнике. Точная формулировка важна для поиска, потому что LINQ выбрасывает из одного и того же класса четыре тесно связанных сообщения, и они означают разное:

Эта статья про первое. Остальные рассмотрены в разделе о вариантах, потому что попасть не на то сообщение означает потратить время впустую.

Почему это происходит

.First() и .Single() являются операторами с контрактом: их тип возврата это non-nullable TSource, поэтому у них нет способа просигнализировать “здесь ничего нет”, кроме как выбросив исключение. Когда источник пуст, возвращать нечего, а вернуть default(TSource) было бы ложью для ссылочного типа (вы получили бы null там, где сигнатура обещала значение). Поэтому среда выполнения вместо этого выбрасывает InvalidOperationException. Это осознанное проектное решение, а не ошибка: варианты *OrDefault существуют именно для случая, когда пустота допустима.

Запутанная часть в том, что последовательность часто оказывается пустой по причинам, невидимым в месте вызова. Расположенный выше фильтр Where удалил все строки. В таблице базы данных пока нет ни одной подходящей записи. Коллекция была очищена или так и не заполнена, потому что более раннее await тихо завершилось неудачей. Исключение срабатывает на строке .First(), но настоящая причина находится на три строки (или на три вызова метода) раньше. Вот почему “просто оберни это в try/catch” редко бывает верным инстинктом: вы хотите знать, почему последовательность пуста, а не просто проглотить симптом.

Минимальное воспроизведение

Наименьший код, который его выбрасывает:

// .NET 11, C# 14
var numbers = new List<int>();     // empty
int first = numbers.First();       // System.InvalidOperationException: Sequence contains no elements

То же самое происходит, когда фильтр устраняет всё, и это гораздо более частая реальная форма:

// .NET 11, C# 14
var orders = new List<Order>
{
    new(Id: 1, Status: "shipped"),
    new(Id: 2, Status: "shipped"),
};

// No pending orders exist, so the filtered sequence is empty.
Order next = orders.First(o => o.Status == "pending");
// System.InvalidOperationException: Sequence contains no matching element

Обратите внимание, что второе сообщение это вариант no matching element, потому что был передан предикат. Оба происходят из одного семейства ошибок: вы предположили, что хотя бы один элемент там будет, а его не было.

Решение в деталях

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

1. Используйте FirstOrDefault / SingleOrDefault и обработайте пустой случай

Если пустая последовательность является допустимым исходом (строк пока нет, необязательный поиск, запрос, который может ничего не найти), переключитесь на перегрузку *OrDefault и проверьте то, что получаете:

// .NET 11, C# 14
Order? next = orders.FirstOrDefault(o => o.Status == "pending");
if (next is null)
{
    // No pending order. Handle it: return early, use a fallback, log, whatever fits.
    return;
}
Process(next);

FirstOrDefault возвращает default(TSource), когда последовательность пуста: null для ссылочного типа, 0 для int, default для структуры. В кодовой базе с nullable-аннотациями (<Nullable>enable</Nullable>, значение по умолчанию в новых шаблонах .NET 11) компилятор типизирует результат как Order? и будет напоминать вам, пока вы не проверите на null, а это именно та безопасность, которая вам нужна. Не пропускайте проверку: замена First на FirstOrDefault и последующее немедленное разыменование результата просто меняет InvalidOperationException на NullReferenceException строкой позже. Если предупреждения о nullable кажутся шумом, это компилятор указывает на настоящую работу, и это напрямую связано с CS8618 и non-nullable свойствами.

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

// .NET 11, C# 14 -- FirstOrDefault(predicate, defaultValue) added in .NET 6
Order next = orders.FirstOrDefault(o => o.Status == "pending", Order.None);

2. Защитите последовательность перед вызовом First

Когда вам действительно нужен первый элемент, но только если он существует, сначала проверьте на пустоту. Для коллекции в памяти достаточно Count или Any():

// .NET 11, C# 14
if (orders.Count > 0)
{
    Order first = orders.First();   // safe: we know it is non-empty
    Process(first);
}

Предпочитайте Count (или Count > 0) для всего, что реализует ICollection<T>, например List<T> или массива, потому что это O(1). Используйте .Any() для лениво вычисляемого IEnumerable<T>, где вы не можете дёшево получить количество. Не пишите if (orders.Count() > 0) на ленивой последовательности: Count() перечисляет её целиком, тогда как Any() останавливается после первого элемента.

3. Устраните причину пустоты последовательности

Иногда пустота это ошибка, а не допустимое состояние. Если orders.First(o => o.Status == "pending") всегда должен находить строку, но не находит, настоящее исправление находится выше по потоку: слишком строгий фильтр, несовпадение регистра ("Pending" против "pending"), соединение, отбросившее строки, или данные, которые так и не были вставлены. Прибегайте здесь к *OrDefault только после того, как подтвердите, что последовательности разрешено быть пустой. Сокрытие случая “это никогда не должно быть пустым” с помощью FirstOrDefault прячет настоящую ошибку данных или логики и переносит сбой туда, где его труднее диагностировать.

4. Для агрегатов используйте nullable-перегрузку или DefaultIfEmpty

.Average(), .Max(), .Min() и .Sum() разделяют ту же ловушку. .Average() и версии .Max()/.Min() для типов-значений выбрасывают Sequence contains no elements на пустом источнике (.Sum() возвращает 0, что является отдельным сюрпризом). Два чистых решения:

// .NET 11, C# 14
var prices = new List<int>();

// Option A: project to a nullable so the aggregate returns null instead of throwing.
double? avg = prices.Average(p => (int?)p);   // null when empty, no exception

// Option B: supply a fallback element before aggregating.
int max = prices.DefaultIfEmpty(0).Max();     // 0 when empty

DefaultIfEmpty это универсальный аварийный люк: он выдаёт единственный элемент по умолчанию, когда источник пуст, так что любой последующий оператор, включая .First(), видит хотя бы один элемент.

Подводные камни и варианты

Некоторые ситуации порождают это исключение или его близкого родственника по причинам, которые сообщение не проговаривает:

Ментальная модель, которую стоит запомнить: .First() и .Single() это утверждения, что элемент существует. Sequence contains no elements это данное утверждение, потерпевшее неудачу. Решите, законен ли пустой случай. Если да, выразите это через FirstOrDefault/SingleOrDefault и обработайте значение по умолчанию, которое получаете. Если нет, исправьте запрос или данные выше по потоку, чтобы последовательность никогда не была пустой в этой точке, вместо того чтобы замазывать это в месте вызова.

Похожее

Источники

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Назад