Menu

Operador ternario en C# (?:): sintaxis, tipos, anidación y ??

El operador condicional de C# condition ? a : b elige uno de dos valores. Cómo se evalúa, por qué las dos ramas necesitan un tipo común (CS0173) y el cast que lo arregla, ternarios anidados, paréntesis en la interpolación de strings y cuándo ?? y ?. lo expresan mejor.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

El operador condicional, que suele llamarse operador ternario, elige entre dos valores en una sola expresión: condition ? valueIfTrue : valueIfFalse.

Salida:

0 items, shipping 4.99
1 item, shipping 4.99
5 items, shipping 0

La condición debe ser un bool. Una vez evaluada, solo una de las dos ramas se ejecuta; la otra nunca se evalúa. Eso hace que el operador sea seguro para accesos protegidos como list.Count > 0 ? list[0] : "empty", donde evaluar list[0] sobre una lista vacía lanzaría una excepción.

Ternario frente a if/else

El ternario es una expresión: produce un valor que asignas, pasas o devuelves. Una sentencia if ejecuta sentencias. Estos dos métodos hacen lo mismo:

// with if/else
static string Status(int stock)
{
    if (stock > 0)
        return "in stock";
    else
        return "sold out";
}

// with the ternary
static string Status(int stock) => stock > 0 ? "in stock" : "sold out";

Usa el ternario cuando las dos ramas son valores cortos del mismo tipo. Usa if cuando una rama ejecuta varias sentencias, tiene efectos secundarios, o cuando la condición es tan larga que la línea entera ya no cabe con comodidad.

El ternario no puede sustituir a un if que solo realiza acciones. Esto no compila:

stock > 0 ? Ship(order) : Refund(order);
// error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement

Tampoco existe un ternario sin else: ? y : siempre son obligatorios, porque la expresión debe tener un valor en los dos casos.

Las dos ramas necesitan un tipo común

El compilador deduce el tipo de la expresión completa a partir de sus dos ramas. Una rama debe convertirse implícitamente al tipo de la otra. Cuando ninguna lo hace, obtienes CS0173:

var discount = isMember ? 10 : null;
// error CS0173: Type of conditional expression cannot be determined because
// there is no implicit conversion between 'int' and '<null>'

var label = inStock ? 1 : "none";
// error CS0173: ... between 'int' and 'string'

La solución es hacer un cast de una rama al tipo que quieres, para que la otra se convierta a él:

Salida:

no discount
7
3.5

points / 2.0 : 0 no necesita cast: 0 es un int, que se convierte implícitamente a double, así que la expresión es un double.

C# 9 añadió el tipado por destino: cuando la expresión se asigna a un tipo declarado, el compilador usa ese tipo. int? discount = isMember ? 10 : null; compila en C# 9 y posteriores. Con var no hay tipo de destino, así que var discount = isMember ? 10 : null; sigue fallando en todas las versiones.

Precedencia y paréntesis

El operador condicional tiene una precedencia muy baja, menor que +, == y &&. Eso es cómodo en a > b ? a : b, y sorprendente dentro de una concatenación de strings:

Console.WriteLine("Status: " + isActive ? "on" : "off");
// error CS0029: Cannot implicitly convert type 'string' to 'bool'

El compilador lee la condición como "Status: " + isActive, un string. Los paréntesis alrededor del ternario lo arreglan: "Status: " + (isActive ? "on" : "off").

La interpolación de strings tiene su propia trampa. Dentro de { }, los dos puntos inician un especificador de formato ({price:F2}), así que un ternario sin paréntesis confunde al parser. Envuélvelo en paréntesis:

Salida:

2 users online
You have 1 unread message
Status: online

Ternarios anidados

El operador es asociativo por la derecha, así que a ? x : b ? y : z se agrupa como a ? x : (b ? y : z). Una cadena pequeña con una condición por línea es legible:

Salida:

512 B
20 KB
5 MB

Pasados dos o tres niveles, o cuando las condiciones prueban cosas distintas, cambia a if/else if o a una expresión switch. Anidar dentro de la rama verdadera (a ? (b ? x : y) : z) es la versión que antes se vuelve difícil de seguir.

?? y ?. sustituyen a ternarios habituales

Muchos ternarios existen solo para manejar null. C# tiene operadores más cortos para esos casos:

TernarioForma más cortaSignificado
name != null ? name : "guest"name ?? "guest"valor alternativo cuando es null
customer != null ? customer.Email : nullcustomer?.Emailacceso a miembro que tolera null
c != null && c.Address != null ? c.Address.City : "unknown"c?.Address?.City ?? "unknown"los dos combinados
value != null ? value : throw new ArgumentNullException(...)value ?? throw new ArgumentNullException(...)fallar pronto

Salida:

Lena from Berlin
guest from unknown
guest from unknown

Una expresión throw también se permite en cualquiera de las ramas de un ternario: int age = input >= 0 ? input : throw new ArgumentOutOfRangeException(nameof(input));.

El condicional ref

Desde C# 7.2, un ternario puede elegir entre dos variables en lugar de dos valores, y puedes asignar a través del resultado:

// C# 7.2 and later
int wins = 0, losses = 0;
bool won = true;

ref int counter = ref (won ? ref wins : ref losses);
counter++;               // increments wins

Las dos ramas deben ser ref a variables del mismo tipo. Es raro en código de aplicación; existe sobre todo para código sensible al rendimiento que trabaja con structs o arrays grandes sin copiarlos.

Preguntas frecuentes

¿Qué es el operador ternario en C#?

Es el operador condicional condition ? valueIfTrue : valueIfFalse, el único operador de C# con tres operandos. Evalúa la condición y después evalúa y devuelve exactamente uno de los dos valores: string label = age >= 18 ? "adult" : "minor";.

¿Puedo usar un ternario sin else en C#?

No. Las partes ? y : son obligatorias, porque la expresión siempre debe producir un valor. Si solo quieres hacer algo cuando se cumple una condición, usa una sentencia if. El ternario tampoco puede ir solo como sentencia: x > 0 ? A() : B(); es el error CS0201.

¿Por qué obtengo "Type of conditional expression cannot be determined"?

Las dos ramas no tienen un tipo común, por ejemplo var x = found ? 42 : null; (int y null) o found ? 1 : "none". Haz un cast de una rama para que exista un tipo: found ? (int?)42 : null. Desde C# 9, un tipo de destino declarado también funciona: int? x = found ? 42 : null; compila, pero con var sigue fallando.

¿Cómo uso el operador ternario dentro de una interpolación de strings?

Envuélvelo en paréntesis: $"{(count == 1 ? "item" : "items")}". Sin ellos, el : se interpreta como el inicio de un especificador de formato y el código no compila.

¿Se pueden anidar operadores ternarios en C#?

Sí. El operador es asociativo por la derecha, así que a ? x : b ? y : z significa a ? x : (b ? y : z). Un nivel de anidación repartido en líneas separadas se lee bien; las cadenas más profundas quedan más claras como una cadena if/else if o una expresión switch.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR