Un operador recibe uno, dos o tres operandos y produce un valor: a + b, !done, x ?? fallback. Los operadores de C# se parecen mucho a los de C, Java y JavaScript, pero algunos se comportan de formas que sorprenden a los recién llegados, sobre todo la división entera y el signo de %.
Operadores aritméticos
+, -, *, / y % funcionan con todos los tipos numéricos. El tipo del resultado sigue a los operandos: dos valores int producen un int, un int y un double producen un double.
Salida:
22
12
85
3
2
3.4
3.4
3
3.4
La división entera trunca hacia cero. 17 / 5 es 3 y -17 / 5 es -3. Provoca un bug muy común: int percent = done / total * 100; da 0 siempre que done < total, porque done / total ya es 0. Multiplica primero (done * 100 / total) o divide como double.
Dividir un entero o un decimal entre cero lanza DivideByZeroException. Dividir un double entre cero no lanza nada; produce infinito, o NaN con 0.0 / 0.0:
Salida:
True
True
False
DivideByZeroException
El operador módulo y los números negativos
% devuelve el resto de la división, y su resultado toma el signo del operando izquierdo. Esto es distinto de Python y del "mod" matemático, que nunca son negativos con un divisor positivo:
Salida:
1
-1
1
2
even
62 min 5 s
Thu
1.5
Usa el método auxiliar Mod siempre que el lado izquierdo pueda ser negativo y el resultado sea un índice: days[(today + offset) % 7] lanzaría una IndexOutOfRangeException con una suma negativa. Comprobar si un número es impar tiene la misma trampa: n % 2 == 1 es false para los impares negativos; comprueba n % 2 != 0.
Asignación, asignación compuesta e incremento
= asigna. Las formas compuestas +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= combinan una operación con la asignación. ++ y -- suman o restan 1, y tienen formas prefija y postfija que se diferencian en el valor que devuelven:
Salida:
14
start, step
a=5 b=7 i=7
4
El caso de byte muestra una peculiaridad: level = level + 10; no compila, porque byte + int es un int, pero level += 10; sí, porque la asignación compuesta incluye un cast implícito de vuelta al tipo de la variable. Ese cast da la vuelta si hay desbordamiento (250 + 10 se convierte en 4).
Operadores de comparación y lógicos
Los operadores de comparación (==, !=, <, >, <=, >=) devuelven un bool. En los strings, == compara el texto, no las referencias. Los operadores lógicos son ! (no), && (y), || (o) y ^ (o exclusivo).
Salida:
True
no active user
with ||:
evaluated A
with |:
evaluated A
evaluated B
True
&& y || hacen cortocircuito: el operando derecho solo se ejecuta cuando puede cambiar el resultado. Eso es lo que hace segura la expresión user != null && user.IsActive. Los operadores simples & y | con booleanos evalúan siempre los dos lados, lo que rara vez es lo que quieres en una condición.
Operadores bit a bit y de desplazamiento
En los tipos enteros, &, |, ^ y ~ trabajan sobre bits individuales, y << y >> los desplazan. Se usan para indicadores, máscaras, hashing y protocolos de bajo nivel:
Salida:
3
True
False
101
1024
125
5
-1
Para conjuntos de indicadores con nombre, un enum con el atributo [Flags] es más claro que unas constantes enteras, y HasFlag se lee mejor que una máscara. C# 11 añadió >>>, un desplazamiento a la derecha sin signo que rellena con ceros incluso en los números negativos.
Operadores de null
Existen tres operadores para trabajar con valores que pueden ser null:
Salida:
Guest
0
5
a ?? b devuelve a salvo que sea null, y entonces b. a?.Member devuelve null en lugar de lanzar una excepción cuando a es null. C# 8 añadió a ??= b, que asigna b a a solo cuando a es null. Se explican en detalle en tipos nullable.
Precedencia de operadores
Cuando una expresión mezcla operadores, las filas más altas se aplican antes. Los operadores de una misma fila se agrupan de izquierda a derecha, salvo la asignación, ?? y ?:, que se agrupan de derecha a izquierda. (Los operandos en sí se evalúan siempre de izquierda a derecha.)
| Categoría | Operadores |
|---|---|
| Primarios | x.y, x?.y, f(x), a[i], x++, x--, new, typeof, nameof |
| Unarios | +x, -x, !x, ~x, ++x, --x, (T)x, await |
| Multiplicativos | *, /, % |
| Aditivos | +, - |
| Desplazamiento | <<, >>, >>> |
| Relacionales y de prueba de tipo | <, >, <=, >=, is, as |
| Igualdad | ==, != |
| AND bit a bit | & |
| XOR bit a bit | ^ |
| OR bit a bit | | |
| AND condicional | && |
| OR condicional | || |
| Fusión de null | ?? |
| Condicional | c ? a : b |
| Asignación y lambda | =, +=, -=, ??=, => y las demás formas compuestas |
Dos consecuencias que conviene conocer: a + b * c multiplica primero, y x & mask == 0 compara primero (== tiene más precedencia que &), así que significa x & (mask == 0) y no compila con enteros. Escribe (x & mask) == 0. Los paréntesis no cuestan nada y dejan clara la intención.
Sobrecarga de operadores
Tus propios tipos pueden definir qué significan los operadores. Así es como decimal, DateTime y TimeSpan admiten + y <:
Salida:
33.49
True
Los operadores deben ser public static, y algunos van en parejas obligatorias: definir < exige >, y == exige !=. Con == definido, el compilador también avisa hasta que sobrescribas Equals y GetHashCode para que coincidan. Sobrecarga operadores solo donde el significado sea evidente, como con números, vectores y dinero.
Preguntas frecuentes
¿Qué hace % en C#?
% es el operador de resto: 17 % 5 es 2. El resultado tiene el signo del operando izquierdo, así que -7 % 3 es -1, no 2 como en Python. Para un resultado que nunca sea negativo, como el que necesitas al dar la vuelta a un índice, escribe ((a % n) + n) % n. También funciona con double y decimal: 7.5 % 2 es 1,5.
¿Por qué 7 / 2 da 3 en C#?
Cuando los dos operandos son enteros, / hace una división entera y descarta la parte fraccionaria, truncando hacia cero. Haz que un operando sea de coma flotante o decimal para obtener un resultado con decimales: 7 / 2.0 es 3,5, (double)a / b convierte antes de dividir, y 7m / 2 es 3,5 como decimal.
¿Qué diferencia hay entre && y & en C#?
&& es el AND condicional: si el lado izquierdo es false, el lado derecho no se evalúa. & con booleanos evalúa siempre los dos lados, y con enteros es el AND bit a bit. Usa && y || en las condiciones, sobre todo en guardas como user != null && user.IsActive, que con & lanzaría una NullReferenceException.
¿Qué diferencia hay entre i++ y ++i en C#?
Los dos suman 1 a i. La expresión i++ se evalúa al valor antiguo, y ++i al valor nuevo. En una línea sola (i++;) no hay diferencia. Solo importa cuando se usa el resultado, como en int a = i++;, y por eso muchas guías de estilo dejan los incrementos en su propia línea.
¿Cuál es la precedencia de los operadores en C#?
De mayor a menor: primarios (x.y, f(x), a[i], x++), unarios (!, -, ++x, casts), multiplicativos (* / %), aditivos (+ -), desplazamientos, relacionales (< > <= >= is as), igualdad (== !=), &, ^, |, &&, ||, ??, el condicional ?: y, por último, la asignación. Si tienes dudas, añade paréntesis.