Por extraño que parezca, se trata simplemente de seguir las reglas de la especificación del lenguaje C #.
De la sección 7.3.4:
Una operación de la forma x op y, donde op es un operador binario sobrecargable, x es una expresión de tipo X e y es una expresión de tipo Y, se procesa de la siguiente manera:
- Se determina el conjunto de operadores candidatos definidos por el usuario proporcionados por X e Y para el operador de operación op (x, y). El conjunto consiste en la unión de los operadores candidatos proporcionados por X y los operadores candidatos proporcionados por Y, cada uno determinado usando las reglas de §7.3.5. Si X e Y son del mismo tipo, o si X e Y se derivan de un tipo de base común, los operadores candidatos compartidos solo se producen en el conjunto combinado una vez.
- Si el conjunto de operadores candidatos definidos por el usuario no está vacío, este se convierte en el conjunto de operadores candidatos para la operación. De lo contrario, las implementaciones de operaciones de operador binario predefinidas, incluidas sus formas elevadas, se convierten en el conjunto de operadores candidatos para la operación. Las implementaciones predefinidas de un operador dado se especifican en la descripción del operador (§7.8 a §7.12).
- Las reglas de resolución de sobrecarga de §7.5.3 se aplican al conjunto de operadores candidatos para seleccionar el mejor operador con respecto a la lista de argumentos (x, y), y este operador se convierte en el resultado del proceso de resolución de sobrecarga. Si la resolución de sobrecarga no selecciona el mejor operador, se produce un error de tiempo de enlace.
Entonces, repasemos esto a su vez.
X es el tipo nulo aquí, o no es un tipo en absoluto, si quieres pensarlo de esa manera. No proporciona ningún candidato. Y es bool, que no proporciona ningún +operador definido por el usuario . Entonces, el primer paso no encuentra operadores definidos por el usuario.
El compilador luego pasa al segundo punto, mirando a través de las implementaciones de operadores binarios predefinidos y sus formas levantadas. Estos se enumeran en la sección 7.8.4 de la especificación.
Si observa esos operadores predefinidos, el único que es aplicable es string operator +(string x, object y). Entonces, el conjunto de candidatos tiene una sola entrada. Eso hace que el punto final sea muy simple ... la resolución de sobrecarga elige ese operador, dando un tipo de expresión general de string.
Un punto interesante es que esto ocurrirá incluso si hay otros operadores definidos por el usuario disponibles en tipos no mencionados. Por ejemplo:
// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;
Eso está bien, pero no se usa para un literal nulo, porque el compilador no sabe buscar Foo. Solo sabe considerar stringporque es un operador predefinido que se enumera explícitamente en la especificación. (De hecho, es no un operador definido por el tipo de cadena ... 1 ) Esto significa que este dejará de compilar:
// Error: Cannot implicitly convert type 'string' to 'Foo'
Foo f = null + true;
Otros tipos de segundo operando usarán algunos otros operadores, por supuesto:
var x = null + 0; // x is Nullable<int>
var y = null + 0L; // y is Nullable<long>
var z = null + DayOfWeek.Sunday; // z is Nullable<DayOfWeek>
1 Puede que se pregunte por qué no hay un operador + cadena. Es una pregunta razonable, y solo estoy adivinando la respuesta, pero considere esta expresión:
string x = a + b + c + d;
Si stringno tuviera una carcasa especial en el compilador de C #, esto terminaría con la misma eficacia:
string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;
Entonces eso creó dos cadenas intermedias innecesarias. Sin embargo, debido a que hay un soporte especial dentro del compilador, en realidad es capaz de compilar lo anterior como:
string x = string.Concat(a, b, c, d);
que puede crear una sola cadena de exactamente la longitud correcta, copiando todos los datos exactamente una vez. Agradable.