Como null + true é uma string?

112

Já que truenão é um tipo de string, como é null + trueuma string?

string s = true;  //Cannot implicitly convert type 'bool' to 'string'   
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'

Qual é a razão por trás disso?

Javed Akram
fonte
27
Você faz algo que não faz sentido e não gosta da mensagem que o compilador gera? Este é um código C # inválido ... o que exatamente você queria fazer?
Hogan
19
@Hogan: O código parece aleatório e acadêmico o suficiente para que a pergunta seja feita por pura curiosidade. Só meu palpite ...
BoltClock
8
null + true retorna 1 em JavaScript.
Fatih Acet

Respostas:

147

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 stringporque é 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 stringnã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.

Jon Skeet
fonte
'dando um tipo de expressão geral de string' Eu simplesmente não concordo. O erro vem de truenão ser conversível para string. Se a expressão fosse válida, o tipo seria string, mas, neste caso, a falha na conversão para string torna a expressão inteira um erro e, portanto, não tem tipo.
leppie
6
@leppie: A expressão é válida e seu tipo é string. Tente "var x = null + true;" - compila e xé do tipo string. Observe que a assinatura usada aqui é string operator+(string, object)- está convertendo boolpara object(o que é bom), não para string.
Jon Skeet
Obrigado Jon, entenda agora. BTW seção 14.7.4 na versão 2 das especificações.
leppie
@leppie: Isso está na numeração ECMA? Isso sempre foi muito diferente :(
Jon Skeet
47
em 20 minutos. Ele escreveu tudo isso em 20 minutos. Ele deveria escrever um livro ou algo ... oh espere.
Epaga
44

A razão é porque, uma vez que você introduza +, as regras de vinculação do operador C # entram em ação. Ele considerará o conjunto de +operadores disponíveis e selecionará a melhor sobrecarga. Um desses operadores é o seguinte

string operator +(string x, object y)

Essa sobrecarga é compatível com os tipos de argumento na expressão null + true. Portanto, ele é selecionado como o operador e é avaliado essencialmente como ((string)null) + trueaquele que avalia o valor "True".

A seção 7.7.4 das especificações da linguagem C # contém os detalhes dessa resolução.

JaredPar
fonte
1
Percebo que o IL gerado vai direto para chamar Concat. Eu estava pensando que veria uma chamada para a função de operador + lá. Isso seria simplesmente otimização embutida?
quentin-starin
1
@qstarin, acredito que a razão é que não existe realmente um operator+para string. Em vez disso, ele só existe na mente do compilador e simplesmente o traduz em chamadas parastring.Concat
JaredPar
1
@qstarin @JaredPar: Falei mais sobre isso na minha resposta.
Jon Skeet
11

O compilador sai em busca de um operador + () que pode receber um argumento nulo primeiro. Nenhum dos tipos de valor padrão se qualificam, nulo não é um valor válido para eles. A única correspondência é System.String.operator + (), não há ambigüidade.

O segundo argumento desse operador também é uma string. Isso é kapooey, não é possível converter bool em string implicitamente.

Hans Passant
fonte
10

Curiosamente, usando o Reflector para inspecionar o que é gerado, o seguinte código:

string b = null + true;
Console.WriteLine(b);

é transformado nisso pelo compilador:

Console.WriteLine(true);

O raciocínio por trás dessa "otimização" é um pouco estranho, devo dizer, e não rima com a seleção de operador que eu esperava.

Além disso, o seguinte código:

var b = null + true; 
var sb = new StringBuilder(b);

é transformado em

string b = true; 
StringBuilder sb = new StringBuilder(b);

onde, string b = true;na verdade, não é aceito pelo compilador.

Peter Lillevold
fonte
8

nullserá convertido em string nula, e há um conversor implícito de bool para string, então o trueserá convertido para string e, em seguida,+ operador será aplicado: é como: string str = "" + true.ToString ();

se você verificar com Ildasm:

string str = null + true;

é como abaixo:

.locals init ([0] string str)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  box        [mscorlib]System.Boolean
  IL_0007:  call       string [mscorlib]System.String::Concat(object)
  IL_000c:  stloc.0
Saeed Amiri
fonte
5
var b = (null + DateTime.Now); // String
var b = (null + 1);            // System.Nullable<Int32> | same with System.Single, System.Double, System.Decimal, System.TimeSpan etc
var b = (null + new Object()); // String | same with any ref type

Louco?? Não, deve haver uma razão por trás disso.

Alguém ligou Eric Lippert...

deciclone
fonte
5

A razão para isso é a conveniência (concatenar strings é uma tarefa comum).

Como BoltClock disse, o operador '+' é definido em tipos numéricos, strings, e pode ser definido para nossos próprios tipos também (sobrecarga de operador).

Se não houver um operador '+' sobrecarregado nos tipos do argumento e eles não forem tipos numéricos, o padrão do compilador é a concatenação de string.

O compilador insere uma chamada para String.Concat(...)quando você concatena usando '+', e a implementação de Concat chama ToString em cada objeto passado para ele.

Quentin-Starin
fonte