Por mais bizarro que possa parecer, é simplesmente seguir as regras das especificações da linguagem C #.
Da seção 7.3.4:
Uma operação da forma x op y, em que op é um operador binário sobrecarregável, x é uma expressão do tipo X e y é uma expressão do tipo Y, é processada da seguinte maneira:
- O conjunto de candidatos a operadores definidos pelo usuário fornecidos por X e Y para o operador de operação op (x, y) é determinado. O conjunto consiste na união dos candidatos a operadores fornecidos por X e dos candidatos a operadores fornecidos por Y, cada um determinado usando as regras de §7.3.5. Se X e Y forem do mesmo tipo, ou se X e Y forem derivados de um tipo de base comum, os operadores candidatos compartilhados só ocorrerão no conjunto combinado uma vez.
- Se o conjunto de candidatos a operadores definidos pelo usuário não estiver vazio, ele se tornará o conjunto de candidatos a operadores para a operação. Caso contrário, as implementações op de operador binário predefinidas, incluindo suas formas levantadas, tornam-se o conjunto de operadores candidatos para a operação. As implementações predefinidas de um determinado operador são especificadas na descrição do operador (§7.8 a §7.12).
- As regras de resolução de sobrecarga de §7.5.3 são aplicadas ao conjunto de operadores candidatos para selecionar o melhor operador com relação à lista de argumentos (x, y), e esse operador se torna o resultado do processo de resolução de sobrecarga. Se a resolução de sobrecarga falhar em selecionar um único melhor operador, ocorre um erro de tempo de ligação.
Então, vamos examinar isso por sua vez.
X é o tipo nulo aqui - ou não é um tipo, se você quiser pensar dessa forma. Não está fornecendo candidatos. Y is bool
, que não fornece nenhum +
operador definido pelo usuário . Portanto, a primeira etapa não encontra operadores definidos pelo usuário.
O compilador então segue para o segundo ponto, examinando as implementações de operador + binário predefinido e suas formas levantadas. Eles estão listados na seção 7.8.4 das especificações.
Se você examinar esses operadores predefinidos, o único aplicável é string operator +(string x, object y)
. Portanto, o conjunto de candidatos tem uma única entrada. Isso torna o marcador final muito simples ... a resolução de sobrecarga seleciona esse operador, fornecendo um tipo de expressão geral de string
.
Um ponto interessante é que isso ocorrerá mesmo se houver outros operadores definidos pelo usuário disponíveis nos tipos não mencionados. Por exemplo:
// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;
Tudo bem, mas não é usado para um literal nulo, porque o compilador não sabe o que fazer Foo
. Ele só sabe que deve considerar string
porque é um operador predefinido explicitamente listado na especificação. (Na verdade, é não um operador definido pelo tipo string ... 1 ) Isso significa que este não será compilado:
// Error: Cannot implicitly convert type 'string' to 'Foo'
Foo f = null + true;
Outros tipos de segundo operando usarão alguns outros operadores, é claro:
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 Você pode estar se perguntando por que não existe um operador string +. É uma pergunta razoável e estou apenas adivinhando a resposta, mas considere esta expressão:
string x = a + b + c + d;
Se string
não houvesse nenhum caso especial no compilador C #, isso seria tão eficaz:
string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;
Então isso criou duas strings intermediárias desnecessárias. No entanto, como há suporte especial dentro do compilador, ele é realmente capaz de compilar o acima como:
string x = string.Concat(a, b, c, d);
que pode criar apenas uma única string com o comprimento exato, copiando todos os dados exatamente uma vez. Agradável.