Start Debugging

Solución: System.InvalidOperationException: Sequence contains no elements

Esta excepción significa que llamaste a .First() o .Single() sobre una secuencia vacía. Usa FirstOrDefault/SingleOrDefault y comprueba el null, protege la consulta o corrige por qué la fuente está vacía.

System.InvalidOperationException: Sequence contains no elements significa que llamaste a .First(), .Single(), .Last() o a alguno de sus primos de agregación (.Average(), .Max(), .Min()) sobre una secuencia que resultó estar vacía. El operador prometió devolver un elemento y no había ninguno, así que lanzó la excepción. La solución es decidir qué debe significar “vacío” para esa llamada: si estar vacío es un resultado normal, cambia a .FirstOrDefault() / .SingleOrDefault() y maneja el null (o el valor por defecto) que recibes de vuelta; si estar vacío es un error, corrige la consulta o los datos para que la secuencia nunca esté vacía en ese punto. Esto se verificó con .NET 11, C# 14 y EF Core 11.0.0, pero el mensaje y el comportamiento han sido estables desde que LINQ llegó en .NET Framework 3.5, así que la guía aplica a cualquier versión.

El error en contexto

La excepción completa, lanzada desde dentro de System.Linq, se ve así:

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

La pista está en el frame superior: System.Linq.ThrowHelper.ThrowNoElementsException. Si lo ves en la traza de pila, un operador de LINQ que devuelve elementos se ejecutó sobre una fuente vacía. La redacción exacta importa para la búsqueda, porque LINQ lanza cuatro mensajes estrechamente relacionados desde la misma clase y significan cosas distintas:

Este artículo trata del primero. Los demás se cubren en la sección de variantes, porque caer en el equivocado te hace perder tiempo.

Por qué ocurre

.First() y .Single() son operadores con contrato: su tipo de retorno es un TSource no anulable, así que no tienen forma de señalar “no hay nada aquí” salvo lanzando una excepción. Cuando la fuente está vacía, no hay ningún elemento que devolver, y retornar default(TSource) sería una mentira para un tipo de referencia (recibirías null donde la firma prometía un valor). Por eso el runtime lanza InvalidOperationException en su lugar. Es una decisión de diseño deliberada, no un error: las variantes *OrDefault existen precisamente para el caso en que estar vacío es aceptable.

La parte confusa es que la secuencia suele estar vacía por razones invisibles en el sitio de la llamada. Un filtro Where anterior eliminó todas las filas. Una tabla de base de datos aún no tiene ningún registro coincidente. Una colección fue vaciada, o nunca se pobló porque un await anterior falló en silencio. La excepción se dispara en la línea del .First(), pero la causa real está tres líneas (o tres llamadas de método) antes. Por eso “simplemente envuélvelo en try/catch” rara vez es el instinto correcto: quieres saber por qué la secuencia está vacía, no solo tragarte el síntoma.

Reproducción mínima

El código más pequeño que la lanza:

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

Lo mismo ocurre cuando un filtro elimina todo, que es la forma real mucho más común:

// .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

Fíjate en que el segundo mensaje es la variante no matching element, porque se pasó un predicado. Ambos vienen de la misma familia de errores: asumiste que al menos un elemento estaría ahí, y no lo estaba.

La solución, en detalle

Recorre estas opciones en orden. Las dos primeras cubren casi todos los casos reales.

1. Usa FirstOrDefault / SingleOrDefault y maneja el caso vacío

Si una secuencia vacía es un resultado legítimo (aún no hay filas, una búsqueda opcional, una consulta que puede no encontrar nada), cambia a la sobrecarga *OrDefault y comprueba lo que recibes:

// .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 devuelve default(TSource) cuando la secuencia está vacía: null para un tipo de referencia, 0 para int, default para un struct. En una base de código con anotaciones anulables (<Nullable>enable</Nullable>, lo predeterminado en las nuevas plantillas de .NET 11), el compilador tipa el resultado como Order? y te insistirá hasta que compruebes el null, que es exactamente la seguridad que quieres. No omitas la comprobación: reemplazar First por FirstOrDefault y luego desreferenciar el resultado de inmediato solo cambia InvalidOperationException por un NullReferenceException una línea después. Si las advertencias de anulabilidad te parecen ruido, es el compilador señalando el trabajo real, y conecta directamente con CS8618 y las propiedades no anulables.

Desde .NET 6 también existe una sobrecarga que te permite proporcionar tu propio valor por defecto, que es más limpia que una comprobación de null aparte cuando tienes un valor alternativo sensato:

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

2. Protege la secuencia antes de llamar a First

Cuando realmente necesitas el primer elemento pero solo si existe, comprueba primero si está vacía. Para una colección en memoria, Count o Any() basta:

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

Prefiere Count (o Count > 0) para cualquier cosa que implemente ICollection<T>, como List<T> o un arreglo, porque es O(1). Usa .Any() para un IEnumerable<T> de evaluación diferida donde no puedes obtener un conteo de forma barata. No escribas if (orders.Count() > 0) sobre una secuencia diferida: Count() la enumera entera, mientras que Any() se detiene después del primer elemento.

3. Corrige por qué la secuencia está vacía

A veces estar vacío es el error, no un estado válido. Si orders.First(o => o.Status == "pending") siempre debería encontrar una fila y no lo hace, la solución real está aguas arriba: un filtro demasiado estricto, una discrepancia de mayúsculas y minúsculas ("Pending" vs "pending"), una unión que descartó filas, o datos que nunca se insertaron. Recurre aquí a un *OrDefault solo después de haber confirmado que se permite que la secuencia esté vacía. Ocultar un caso de “esto nunca debería estar vacío” con FirstOrDefault esconde un error genuino de datos o de lógica y mueve el fallo a un lugar más difícil de diagnosticar.

4. Para las agregaciones, usa una sobrecarga anulable o DefaultIfEmpty

.Average(), .Max(), .Min() y .Sum() comparten la misma trampa. .Average() y las versiones de tipo de valor de .Max()/.Min() lanzan Sequence contains no elements sobre una fuente vacía (.Sum() devuelve 0, que es su propia sorpresa). Dos soluciones limpias:

// .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 es la escotilla de escape de propósito general: produce un único elemento por defecto cuando la fuente está vacía, de modo que cualquier operador posterior, incluido .First(), ve al menos un elemento.

Trampas y variantes

Algunas situaciones producen esta excepción, o una pariente cercana, por razones que el mensaje no deletrea:

El modelo mental que hay que retener: .First() y .Single() son afirmaciones de que un elemento existe. Sequence contains no elements es esa afirmación fallando. Decide si el caso vacío es legal. Si lo es, exprésalo con FirstOrDefault/SingleOrDefault y maneja el valor por defecto que recibes. Si no lo es, corrige la consulta o los datos aguas arriba para que la secuencia nunca esté vacía en ese punto, en lugar de disimularlo en el sitio de la llamada.

Relacionados

Fuentes

Comments

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

< Volver