Por que é válido concatenar cadeias nulas, mas não chamar "null.ToString ()"?

126

Este é um código C # válido

var bob = "abc" + null + null + null + "123";  // abc123

Este não é um código C # válido

var wtf = null.ToString(); // compiler error

Por que a primeira declaração é válida?

superlógico
fonte
97
Acho peculiar que você null.ToString()receba esse nome wtf. Por que isso te surpreende? Você não pode chamar um método de instância quando não tem nada para chamá-lo.
BoltClock
11
@BoltClock: Claro que é possível chamar um método de instância em uma instância nula. Simplesmente não é possível em C #, mas muito válida no CLR :)
leppie
1
As respostas em relação ao String.Concat estão quase corretas. De fato, o exemplo específico da pergunta é de dobragem constante , e os nulos na primeira linha são eliminados pelo compilador - ou seja, eles nunca são avaliados em tempo de execução porque não existem mais - o compilador os apagou. Eu escrevi um pequeno monólogo sobre todas as regras para concatenação e dobragem constante de Strings aqui para mais informações: stackoverflow.com/questions/9132338/… .
22630 Chris Shain
77
você não pode adicionar nada a uma string, mas não pode transformá-la em nada.
RGB
A segunda declaração não é válida? class null_extension { String ToString( Object this arg ) { return ToString(arg); } }
CTRL-ALT-DELOR

Respostas:

163

O motivo do primeiro trabalho:

Do MSDN :

Nas operações de concatenação de cadeias, o compilador C # trata uma cadeia nula da mesma forma que uma cadeia vazia, mas não converte o valor da cadeia nula original.

Mais informações sobre o operador binário + :

O operador binário + executa concatenação de cadeia de caracteres quando um ou ambos os operandos são do tipo cadeia de caracteres.

Se um operando de concatenação de sequência for nulo, uma sequência vazia será substituída. Caso contrário, qualquer argumento não-string será convertido em sua representação de string chamando o ToStringmétodo virtual herdado do objeto de tipo.

Se ToStringretornar null, uma sequência vazia será substituída.

A razão do erro no segundo é:

null (Referência de C #) - A palavra-chave null é um literal que representa uma referência nula, que não se refere a nenhum objeto. null é o valor padrão das variáveis ​​do tipo de referência.

Pranay Rana
fonte
1
Isso funcionaria então? var wtf = ((String)null).ToString();Estou trabalhando em Java recentemente, onde a conversão de nulos é possível, já faz um tempo desde que trabalhei com C #.
Jochem
14
@Jochem: Você ainda está tentando chamar um método em um objeto nulo, então acho que não. Pense nisso como null.ToString()vs ToString(null).
Svish
@ Skish sim, agora penso nisso novamente, é um objeto nulo, então você está certo, não vai funcionar. Também não seria em Java: exceção de ponteiro nulo. Deixa pra lá. Tnx para sua resposta! [edit: testou em Java: NullPointerException. Com a diferença de que com o elenco ele compila, sem o elenco não].
Jochem
geralmente não há motivo para concatenar intencionalmente ou ToString a palavra-chave nula. Se você precisar de uma string vazia, use string.Empty. se você precisar verificar se uma variável de cadeia é nula, use (myString == null) ou string.IsNullOrEmpty (myString). Alternativamente, para transformar uma variável de cadeia nula em string.Empty uso myNewString = (minhaString == null ?? string.Empty)
csauve
@Jochem lançando ele irá torná-lo compilar mas ele vai realmente lançar um NullReferenceException em tempo de execução
jeroenh
108

Como o +operador em C # traduz internamente para String.Concat, que é um método estático. E esse método é tratado nullcomo uma string vazia. Se você olhar a fonte do String.ConcatReflector, verá:

// while looping through the parameters
strArray[i] = (str == null) ? Empty : str;
// then concatenate that string array

(O MSDN também menciona: http://msdn.microsoft.com/en-us/library/k9c94ey1.aspx )

Por outro lado, ToString()é um método de instância que você não pode chamar null(que tipo deve ser usado null?).

Botz3000
fonte
2
No entanto, não há + operador na classe String .. Então é o compilador traduzindo para String.Concat?
Superlogical
@ superlogical Sim, é isso que o compilador faz. Então, na verdade, o +operador em strings em C # é apenas açúcar sintático para String.Concat.
Botz3000
Além disso: o compilador seleciona automaticamente a sobrecarga do Concat que faz mais sentido. As sobrecargas disponíveis são 1, 2 ou 3 parâmetros de objeto, 4 parâmetros de objeto + __argliste uma paramsversão da matriz de objetos.
Wormbo
Qual array de strings está sendo modificado lá? Será Concatsempre criar uma nova matriz de strings não nulos, mesmo quando dado um array de strings não nulos, ou é outra coisa acontecendo?
Supercat
@supercat O array modificado é um novo array, que é passado para um método auxiliar privado. Você pode ver o código usando refletor.
precisa saber é o seguinte
29

A primeira amostra será traduzida para:

var bob = String.Concat("abc123", null, null, null, "abs123");

O Concatmétodo verifica a entrada e converte null como uma string vazia

A segunda amostra será traduzida para:

var wtf = ((object)null).ToString();

Portanto, uma nullexceção de referência será gerada aqui

Viacheslav Smityukh
fonte
Na verdade, um AccessViolationExceptionserá lançada :)
leppie
Acabei de fazer :) ((object)null).ToString()=> AccessViolation ((object)1 as string).ToString()=>NullReference
leppie
1
Eu tenho NullReferenceException. DN 4.0
Viacheslav Smityukh
Interessante. Quando eu coloco ((object)null).ToString()dentro de um try/catcheu NullReferenceExceptiontambém.
Leppie
3
@ Ieppie, exceto que não gera AccessViolation (por que seria?) - NullReferenceException aqui.
Jeroenh # 30/12
11

A primeira parte do seu código é tratada dessa maneira em String.Concat,

que é o que o compilador C # chama quando você adiciona strings. " abc" + nullé traduzido para String.Concat("abc", null),

e internamente, esse método substitui nullpor String.Empty. Portanto, é por isso que sua primeira parte do código não gera nenhuma exceção. é como

var bob = "abc" + string.Empty + string.Empty + string.Empty + "123";  //abc123

E na segunda parte do seu código lança exceção porque 'null' não é um objeto, a palavra-chave null é um literal que representa uma referência nula , que não se refere a nenhum objeto. null é o valor padrão das variáveis ​​do tipo de referência.

E ' ToString()' é um método que pode ser chamado por uma instância de um objeto, mas não por nenhum literal.

Talha
fonte
3
String.Emptynão é igual a null. É apenas tratado da mesma maneira em alguns métodos como String.Concat. Por exemplo, se você tiver uma variável de cadeia definida como nullC #, ela não será substituída por String.Emptyse você tentar chamar um método.
Botz3000
3
Quase. nullnão é tratado como String.Emptyem C #, é apenas tratado dessa maneira String.Concat, que é o que o compilador C # chama quando você adiciona strings. "abc" + nullé traduzido String.Concat("abc", null)e internamente esse método substitui nullpor String.Empty. A segunda parte está completamente correta.
Botz3000
9

Na estrutura COM que precedeu o .net, era necessário que qualquer rotina que recebesse uma sequência liberasse-a quando ela terminasse. Como era muito comum as seqüências de caracteres vazias serem passadas para dentro e fora das rotinas, e porque a tentativa de "liberar" um ponteiro nulo era definida como uma operação legítima do tipo nada, a Microsoft decidiu que um ponteiro de seqüência de caracteres nula representasse uma seqüência de caracteres vazia.

Para permitir alguma compatibilidade com o COM, muitas rotinas no .net interpretarão um objeto nulo como uma representação legal como uma seqüência de caracteres vazia. Com algumas pequenas alterações .net e seus idiomas (principalmente permitindo que os membros da instância indiquem "não invocam como virtuais"), a Microsoft poderia ter feito com que nullobjetos do tipo declarado String se comportassem ainda mais como cadeias vazias. Se a Microsoft tivesse feito isso, também teria que fazer o Nullable<T>trabalho de maneira um pouco diferente (para permitir - Nullable<String>algo que eles deveriam ter feito de qualquer maneira) - e / ou definir um NullableStringtipo que seria mais intercambiável String, mas que não consideraria um nullcomo uma sequência vazia válida.

Como é, há alguns contextos nos quais a nullserá considerado como uma string vazia legítima e outros nos quais não será. Não é uma situação terrivelmente útil, mas que os programadores devem conhecer. Em geral, as expressões do formulário stringValue.someMemberfalharão se stringValueestiverem null, mas a maioria dos métodos e operadores de estrutura que aceitam cadeias de caracteres como parâmetros considerarão nulluma cadeia vazia.

supercat
fonte
5

'+'é um operador infix. Como qualquer operador, está realmente chamando um método. Você pode imaginar uma versão não infix"wow".Plus(null) == "wow"

O implementador decidiu algo assim ...

class String
{
  ...
  String Plus(ending)
  {
     if(ending == null) return this;
     ...
  }
} 

Então .. seu exemplo se torna

var bob = "abc".Plus(null).Plus(null).Plus(null).Plus("123");  // abc123

que é o mesmo que

var bob = "abc".Plus("123");  // abc123

Em nenhum momento o null se torna uma string. Então null.ToString()não é diferente isso null.VoteMyAnswer(). ;)

Nigel Thorne
fonte
Realmente, é mais parecido var bob = Plus(Plus(Plus(Plus("abc",null),null),null),"123");. As sobrecargas do operador são métodos estáticos em seu coração: msdn.microsoft.com/en-us/library/s53ehcz3(v=VS.71).aspx Eles não eram var bob = null + "abc";ou , especialmente string bob = null + null;, não seriam válidos.
precisa
Você está certo, eu apenas senti que seria muito confuso se explicasse dessa maneira.
Nigel Thorne
3

Eu acho que porque é um literal que não se refere a nenhum objeto. ToString()precisa de um object.

Saeed Neamati
fonte
3

Alguém disse neste tópico de discussão que você não pode criar uma string do nada. (que é uma frase legal como eu acho). Mas sim - você pode :-), como mostra o exemplo a seguir:

var x = null + (string)null;     
var wtf = x.ToString();

funciona bem e não lança uma exceção. A única diferença é que você precisa converter um dos nulos em uma string - se você remover a string (string) , o exemplo ainda será compilado, mas lançará uma exceção em tempo de execução: "Operator '+' é ambíguo nos operandos de digite '<null>' e '<null>' ".

NB No exemplo de código acima, o valor de x não é nulo como você poderia esperar; na verdade, é uma sequência vazia depois de converter um dos operandos em uma sequência.


Outro fato interessante é que, em C # / .NET, a maneira como nullé tratada nem sempre é a mesma se você considerar diferentes tipos de dados. Por exemplo:

int? x = 1;  //  string x = "1";
x = x + null + null;
Console.WriteLine((x==null) ? "<null>" : x.ToString());

Considere a 1ª linha do trecho de código: se xfor uma variável inteira anulável (ou seja int?) que contém valor 1, você estará recebendo o resultado de <null>volta. Se for uma string (como mostrado no comentário) com valor "1", você estará "1"retornando em vez de <null>.

Nota: também interessante: se você estiver usando var x = 1;a primeira linha, receberá um erro de tempo de execução. Por quê? Porque a atribuição transformará a variável xno tipo de dados int, que não é anulável. O compilador não assume int?aqui e, portanto, falha na segunda linha em que nullé adicionado.

Matt
fonte
2

A adição nulla uma string é simplesmente ignorada. null(no seu segundo exemplo) não é uma instância de nenhum objeto, portanto nem sequer possui um ToString()método. É apenas um literal.

Thorsten Dittmar
fonte
1

Porque não há diferença entre string.Emptye nullquando você concata seqüências de caracteres. Você também pode passar nulo para string.Format. Mas você está tentando chamar um método null, o que sempre resultaria em um NullReferenceExceptione, portanto, gera um erro do compilador.
Se, por algum motivo, você realmente quiser fazer isso, escreva um método de extensão que verifique nulle retorne string.Empty. Mas uma extensão como essa só deve ser usada quando for absolutamente necessária (na minha opinião).

Franky
fonte
0

Como geral: pode ou não ser válido aceitar nulo como parâmetro, dependendo da especificação, mas é sempre inválido chamar um método como nulo.


Esse é outro tópico do motivo pelo qual os operandos do operador + podem ser nulos no caso de strings. Isso é meio coisa de VB (desculpe pessoal) para facilitar a vida dos programadores, ou supondo que o programador não possa lidar com nulos. Eu discordo completamente desta especificação. 'desconhecido' + 'qualquer coisa' ainda deve ser 'desconhecido' ...

g.pickardou
fonte