Por que o valor padrão do tipo de sequência é nulo em vez de uma sequência vazia?

218

É muito chato testar todas as minhas strings nullantes que eu possa aplicar com segurança métodos como ToUpper(), StartWith()etc ...

Se o valor padrão de stringfosse a sequência vazia, eu não precisaria testar e sentiria que era mais consistente com os outros tipos de valor, como intou doublepor exemplo. Além disso Nullable<String>, faria sentido.

Então, por que os designers de C # optaram por usar nullcomo o valor padrão de strings?

Nota: Isso está relacionado a esta questão , mas é mais focado no porquê, em vez do que fazer com ele.

Marcel
fonte
53
Você considera isso um problema para outros tipos de referência?
precisa saber é o seguinte
17
@ JonSkeet Não, mas apenas porque eu, inicialmente, erradamente, pensei que cadeias são tipos de valor.
Marcel
21
@ Marcel: Essa é uma boa razão para pensar sobre isso.
TJ Crowder
7
@JonSkeet Sim. Ai sim. (Mas você não é estranho para o tipo de referência discussão não nulo ...)
Konrad Rudolph
7
Eu acredito que você teria um tempo muito melhor se usasse asserções em suas strings em lugares onde você espera que elas não sejam null(e também recomendo que você trate conceitualmente nulle esvazie as strings como coisas diferentes). Um valor nulo pode ser o resultado de um erro em algum lugar, enquanto uma string vazia deve transmitir um significado diferente.
precisa saber é o seguinte

Respostas:

312

Por que o valor padrão do tipo de sequência é nulo em vez de uma sequência vazia?

Porque stringé um tipo de referência e o valor padrão para todos os tipos de referência é null.

É muito irritante testar todas as minhas strings como null antes que eu possa aplicar com segurança métodos como ToUpper (), StartWith () etc ...

Isso é consistente com o comportamento dos tipos de referência. Antes de chamar os membros da instância, deve-se colocar uma verificação para uma referência nula.

Se o valor padrão de string fosse vazio, eu não precisaria testar e sentiria que era mais consistente com os outros tipos de valores, como int ou double, por exemplo.

Atribuir o valor padrão a um tipo de referência específico que nullnão o tornaria inconsistente .

Além disso Nullable<String>, faria sentido.

Nullable<T>trabalha com os tipos de valor. É digno de nota o fato de que Nullablenão foi introduzido na plataforma .NET original; portanto, haveria muito código quebrado se eles alterassem essa regra. ( Cortesia: @jcolebrand )

Habib
fonte
10
@HenkHolterman Alguém poderia implementar uma tonelada de coisas, mas por que introduzir uma inconsistência tão flagrante?
4
@ delnan - "por que" foi a pergunta aqui.
Henk Holterman
8
@HenkHolterman E "Consistência" é a refutação ao seu ponto "a string pode ser tratada diferentemente de outros tipos de referência".
6
@ delnan: Ao trabalhar em uma linguagem que trata as strings como tipos de valor e a trabalhar mais de 2 anos no dotnet, concordo com Henk. Eu vejo isso como uma grande falha no dotnet.
Fabricio Araujo 15/01
1
@ delnan: Pode-se criar um tipo de valor que se comporte essencialmente como String, exceto por (1) o comportamento de valor-tipo-ish de ter um valor padrão utilizável e (2) uma infeliz camada extra de indireção de boxe a qualquer momento em que foi lançada. Object. Dado que a representação de heap de stringé única, ter tratamento especial para evitar boxe extra não seria muito exagerado (na verdade, ser capaz de especificar comportamentos de boxe não padrão também seria bom para outros tipos).
Supercat
40

Habib está certo - porque stringé um tipo de referência.

Mais importante, porém, você não precisa verificar nullcada vez que o usa. Você provavelmente deve jogar a ArgumentNullExceptionse alguém passar uma nullreferência à sua função .

Aqui está a coisa - a estrutura lançaria um NullReferenceExceptionpara você de qualquer maneira, se você tentasse chamar .ToUpper()uma string. Lembre-se de que esse caso ainda pode ocorrer mesmo se você testar seus argumentos, nullpois qualquer propriedade ou método nos objetos passados ​​para sua função como parâmetros podem ser avaliados null.

Dito isto, verificar se há cadeias vazias ou nulos é uma coisa comum a ser feita, portanto eles fornecem String.IsNullOrEmpty()e String.IsNullOrWhiteSpace()exatamente para esse fim.

Dave Markle
fonte
30
Você nunca deve lançar um arquivoNullReferenceException ( msdn.microsoft.com/en-us/library/ms173163.aspx ); você lança um ArgumentNullExceptionse seu método não puder aceitar refs nulos. Além disso, os NullRefs são tipicamente uma das exceções mais difíceis de diagnosticar quando você está corrigindo problemas; portanto, não acho que a recomendação de verificar nulo seja muito boa.
Andy
3
@ Andy "NullRef's são normalmente uma das exceções mais difíceis de diagnosticar" Eu discordo totalmente, se você registrar coisas, é realmente fácil encontrar e corrigir (apenas lide com o caso nulo).
Louis Kottmann
6
Jogar ArgumentNullExceptiontem o benefício adicional de poder fornecer o nome do parâmetro. Durante a depuração, isso economiza ... erro, segundos. Mas segundos importantes.
Kos
2
@DaveMarkle, você pode querer incluir também o IsNullOrWhitespace msdn.microsoft.com/en-us/library/…
Nathan Koop
1
Eu realmente acho que procurar nulos em todos os lugares é uma fonte de imenso inchaço de código. é feio, parece hacky e é difícil permanecer consistente. Eu acho que (pelo menos em idiomas semelhantes a C #) uma boa regra é "banir a palavra-chave nula no código de produção, use-a como louca no código de teste".
sara
24

Você pode escrever um método de extensão (pelo que vale a pena):

public static string EmptyNull(this string str)
{
    return str ?? "";
}

Agora isso funciona com segurança:

string str = null;
string upper = str.EmptyNull().ToUpper();
Tim Schmelter
fonte
100
Mas por favor não. A última coisa que outro programador deseja ver são milhares de linhas de código salpicadas com .EmptyNull () em todos os lugares apenas porque o primeiro cara estava "assustado" com exceções.
Dave Markle
15
@ DaveMarkle: Mas obviamente é exatamente o que o OP estava procurando. "É muito chato testar todas as minhas strings como nulas antes que eu possa aplicar com segurança métodos como ToUpper (), StartWith () etc"
Tim Schmelter 15/01/13
19
O comentário foi para o OP, não para você. Embora sua resposta esteja claramente correta, um programador que faça uma pergunta básica como essa deve ser fortemente advertido contra realmente colocar sua solução em prática LARGA, como costuma acontecer. Existem várias vantagens e desvantagens que você não discute em sua resposta, como opacidade, complexidade aumentada, dificuldade de refatoração, uso excessivo em potencial de métodos de extensão e, sim, desempenho. Às vezes (muitas vezes) uma resposta correta não é o caminho certo, e é por isso que comentei.
Dave Markle
5
@ Andy: A solução para não ter uma verificação nula adequada é verificar se há nulos corretamente, não colocar um band-aid em um problema.
Dave Markle
7
Se você está enfrentando problemas para escrever .EmptyNull(), por que não simplesmente usar (str ?? "")onde é necessário? Dito isto, concordo com o sentimento expresso no comentário de @ DaveMarkle: você provavelmente não deveria. nulle String.Emptysão conceitualmente diferentes, e você não pode necessariamente tratar um da mesma forma que o outro.
a CVn
17

Você também pode usar o seguinte, a partir do C # 6.0

string myString = null;
string result = myString?.ToUpper();

O resultado da sequência será nulo.

russelrillema
fonte
1
Para ser correto, desde o c # 6.0, a versão do IDE não tem nada a ver com isso, pois esse é um recurso de linguagem.
Stijn Van Antwerpen
3
Outra opção -public string Name { get; set; } = string.Empty;
Jaja Harris
Como isso é chamado? myString? .ToUpper ();
Hunter Nelson
1
É chamado de operador nulo-condicional. Você pode ler sobre isso aqui msdn.microsoft.com/en-us/magazine/dn802602.aspx
russelrillema
14

Seqüências de caracteres vazias e nulos são fundamentalmente diferentes. Um nulo é a ausência de um valor e uma sequência vazia é um valor vazio.

A linguagem de programação que faz suposições sobre o "valor" de uma variável, nesse caso, uma sequência vazia, será tão boa quanto iniciar a sequência com qualquer outro valor que não cause um problema de referência nulo.

Além disso, se você passar o identificador para essa variável de cadeia de caracteres para outras partes do aplicativo, esse código não terá como validar se você passou intencionalmente um valor em branco ou se esqueceu de preencher o valor dessa variável.

Outra ocasião em que isso seria um problema é quando a string é um valor de retorno de alguma função. Como string é um tipo de referência e tecnicamente pode ter um valor nulo e vazio, ambos, portanto, a função também pode retornar tecnicamente nulo ou vazio (não há nada para impedir isso). Agora, como existem 2 noções de "ausência de valor", ou seja, uma string vazia e uma nula, todo o código que consome esta função terá que fazer 2 verificações. Um para vazio e outro para nulo.

Em resumo, é sempre bom ter apenas 1 representação para um único estado. Para uma discussão mais ampla sobre vazios e nulos, consulte os links abaixo.

/software/32578/sql-empty-string-vs-null-value

NULL vs Vazio ao lidar com a entrada do usuário

Nerrve
fonte
2
E como exatamente você vê essa diferença, digamos em uma caixa de texto? O usuário esqueceu de inserir um valor no campo ou o deixou intencionalmente em branco? Nulo em uma linguagem de programação tem um significado específico; não atribuído. Sabemos que ele não tem um valor, que não é o mesmo que um nulo no banco de dados.
Andy
1
Não há muita diferença quando você o usa com uma caixa de texto. De qualquer maneira, ter uma notação para representar a ausência de um valor em uma string é fundamental. Se eu tivesse que escolher um, eu escolheria nulo.
Nerrve
No Delphi, string é um tipo de valor e, portanto, não pode ser nulo. Isso facilita muito a vida a esse respeito - eu realmente acho muito irritante fazer da string um tipo de referência.
Fabricio Araujo 15/01
1
Sob o COM (Common Object Model), que antecede o .net, um tipo de string pode conter um ponteiro para os dados da string ou nullrepresentar a string vazia. Existem várias maneiras pelas quais .net poderia ter implementado semânticas semelhantes, caso elas tivessem optado por fazê-lo, especialmente considerando que Stringpossui várias características que o tornam um tipo único de qualquer maneira [por exemplo, ele e os dois tipos de matriz são os únicos tipos cuja alocação tamanho não é constante].
supercat
7

A razão / problema fundamental é que os projetistas da especificação CLS (que definem como as linguagens interagem com .net) não definiram um meio pelo qual os membros da classe pudessem especificar que deveriam ser chamados diretamente, e não via callvirt, sem que o chamador realizasse uma chamada. verificação de referência nula; nem forneceu um meio de definir estruturas que não estariam sujeitas ao boxe "normal".

Se a especificação CLS definisse esses meios, seria possível que o .net seguisse consistentemente o lead estabelecido pelo Common Object Model (COM), sob o qual uma referência de cadeia nula fosse considerada semanticamente equivalente a uma cadeia vazia e por outras tipos de classe imutáveis ​​definidos pelo usuário que deveriam ter semântica de valores para definir da mesma forma os valores padrão. Essencialmente, o que aconteceria seria para cada membro String, por exemplo, Lengthser escrito como algo parecido [InvokableOnNull()] int String Length { get { if (this==null) return 0; else return _Length;} }. Essa abordagem ofereceria semântica muito boa para coisas que deveriam se comportar como valores, mas, devido a problemas de implementação, precisam ser armazenados no heap. A maior dificuldade com essa abordagem é que a semântica da conversão entre esses tipos e Objectpode ficar um pouco obscura.

Uma abordagem alternativa seria permitir a definição de tipos de estrutura especiais que não foram herdados, Objectmas que tinham operações personalizadas de boxe e unboxing (que seriam convertidas para / de outro tipo de classe). Sob essa abordagem, haveria um tipo de classe NullableStringque se comporta como a string agora e um tipo de estrutura personalizada String, que conteria um único campo particular Valuedo tipo String. A tentativa de converter um Stringpara NullableStringou Objectretornaria Valuese não for nulo ou String.Emptyse for nulo. Tentando converter para String, uma referência não nula a uma NullableStringinstância armazenaria a referência Value(talvez armazenando nulo se o comprimento fosse zero); lançar qualquer outra referência lançaria uma exceção.

Mesmo que as strings precisem ser armazenadas no heap, conceitualmente não há razão para que elas não devam se comportar como tipos de valor que possuem um valor padrão não nulo. Tê-los armazenados como uma estrutura "normal", que continha uma referência, seria eficiente para o código que os utilizava como tipo "string", mas teria adicionado uma camada extra de indireção e ineficiência ao converter para "objeto". Embora eu não preveja o .net adicionando um dos recursos acima até essa data tardia, talvez designers de estruturas futuras possam considerar a possibilidade de incluí-los.

supercat
fonte
1
Falando como alguém que trabalha muito em SQL e lidou com a dor de cabeça da Oracle que não faz distinção entre NULL e comprimento zero, estou muito feliz que o .NET faça . "Vazio" é um valor, "nulo" não é.
@ JonofAllTrades: Eu discordo. No código do aplicativo, exceto ao lidar com o código db, não há sentido em uma string sendo tratada como uma classe. É um tipo de valor e básico. Supercat: +1 para você
Fabricio Araujo 15/01
1
O código do banco de dados é um grande "exceto". Desde que haja alguns domínios com problemas nos quais você precise distinguir entre "presente / conhecido, uma sequência vazia" e "não presente / desconhecido / inaplicável", como bancos de dados, o idioma precisará ser suportado. É claro que agora que o .NET possui Nullable<>, as strings podem ser reimplementadas como tipos de valor; Não posso falar dos custos e benefícios dessa escolha.
3
@ JonofAllTrades: o código que lida com números deve ter um meio fora da banda para distinguir o valor padrão zero de "indefinido". Assim, o código de manipulação anulável que funciona com seqüências de caracteres e números precisa usar um método para seqüências de caracteres anuláveis ​​e outro para números que podem ser anulados. Mesmo que um tipo de classe anulável stringseja mais eficiente do que Nullable<string>seria, ter que usar o método "mais eficiente" é mais oneroso do que poder usar a mesma abordagem para todos os valores de banco de dados anuláveis.
supercat
5

Como uma variável de cadeia é uma referência , não uma instância .

Inicializá-lo como Empty por padrão seria possível, mas introduziria muitas inconsistências em todo o quadro.

Henk Holterman
fonte
3
Não existe um motivo específico stringpara ser um tipo de referência. Certamente, os caracteres reais que compõem a string certamente precisam ser armazenados no heap, mas, dada a quantidade de suporte dedicado que as strings já têm no CLR, não seria um exagero ter System.Stringum tipo de valor com um valor único campo privado Valuedo tipo HeapString. Esse campo seria um tipo de referência e o padrão seria null, mas uma Stringestrutura cujo Valuecampo fosse nulo se comportaria como uma sequência vazia. A única desvantagem dessa abordagem seria ... #
687
1
... que converter um Stringpara Object, na ausência de código de caso especial no tempo de execução, causaria a criação de uma Stringinstância em caixa no heap, em vez de simplesmente copiar uma referência ao HeapString.
Supercat
1
@ supercat - ninguém está dizendo que a string deveria / poderia ser um tipo de valor.
Henk Holterman
1
Ninguém, exceto eu. Ter uma string como um tipo de valor "especial" (com um campo de tipo de referência privado) permitiria que a maioria das manipulações fosse essencialmente tão eficiente quanto agora, exceto por uma verificação nula adicionada a métodos / propriedades como .Lengthetc., para que instâncias que contenham uma referência nula não tentaria desreferenciá-la, mas se comportaria conforme apropriado para uma sequência vazia. Se o Framework seria melhor ou pior se fosse stringimplementado dessa maneira, se alguém quisesse default(string)ser uma string vazia ... #
307
1
... stringser um invólucro de tipo de valor em um campo de tipo de referência seria a abordagem que exigisse o menor número de alterações em outras partes de .net [na verdade, se alguém estivesse disposto a aceitar a conversão de Stringpara Objectcriar um item extra em caixa, alguém poderia simplesmente Stringser uma estrutura comum com um campo do tipo Char[]que nunca foi exposto]. Eu acho que ter um HeapStringtipo provavelmente seria melhor, mas, de certa forma, a cadeia de valor-tipo que contém a Char[]seria mais simples.
Supercat
5

Por que os designers de C # optaram por usar null como o valor padrão de strings?

Como cadeias de caracteres são tipos de referência , os tipos de referência são o valor padrão null. Variáveis ​​de tipos de referência armazenam referências aos dados reais.

Vamos usar a defaultpalavra-chave para este caso;

string str = default(string); 

stré um string, então é um tipo de referência , então o valor padrão é null.

int str = (default)(int);

stré um int, então é um tipo de valor , então o valor padrão é zero.

Soner Gönül
fonte
4

Se o valor padrão de stringfosse a string vazia, eu não precisaria testar

Errado! Alterar o valor padrão não altera o fato de ser um tipo de referência e alguém ainda pode definir explicitamente a referência como sendo null.

Além disso Nullable<String>, faria sentido.

Verdadeiro ponto. Seria mais sensato não permitir nullnenhum tipo de referência, mas exigir Nullable<TheRefType>esse recurso.

Então, por que os designers de C # optaram por usar nullcomo o valor padrão de strings?

Consistência com outros tipos de referência. Agora, por que permitir nulltipos de referência? Provavelmente para parecer C, mesmo que essa seja uma decisão de design questionável em um idioma que também fornece Nullable.

Dan Burton
fonte
3
Pode ser porque o Nullable foi introduzido apenas no .NET 2.0 Framework, então antes disso não estava disponível?
precisa saber é
3
Obrigado Dan Burton por apontar que alguém PODE definir o valor inicializado como nulo nos tipos de referência posteriormente. Pensar sobre isso me diz que minha intenção original na pergunta não leva a nada.
Marcel
4

Talvez se você usaria ??operador ao atribuir a variável de corda, que poderia ajudá-lo.

string str = SomeMethodThatReturnsaString() ?? "";
// if SomeMethodThatReturnsaString() returns a null value, "" is assigned to str.
Amen Jlili
fonte
2

Uma String é um objeto imutável, o que significa que, quando determinado valor, o valor antigo não é apagado da memória, mas permanece no local antigo e o novo valor é colocado em um novo local. Portanto, se o valor padrão de String aera String.Empty, ele desperdiçaria o String.Emptybloco na memória quando receber seu primeiro valor.

Embora pareça minúsculo, pode se transformar em um problema ao inicializar uma grande variedade de cadeias com valores padrão de String.Empty. Claro, você sempre pode usar a StringBuilderclasse mutável se isso for um problema.

djv
fonte
Obrigado por mencionar a coisa da "primeira inicialização".
Marcel
3
Como seria um problema ao inicializar uma grande matriz? Como, como você disse, Strings são imutáveis, todos os elementos da matriz seriam simplesmente ponteiros para o mesmo String.Empty. Estou enganado?
Dan Burton
2
O valor padrão para qualquer tipo terá todos os bits definidos como zero. A única maneira de o valor padrão stringser uma sequência vazia é permitir todos os bits zero como representação de uma sequência vazia. Há várias maneiras de fazer isso, mas acho que nenhuma envolve a inicialização de referências String.Empty.
supercat
Outras respostas discutiram esse ponto também. Acho que as pessoas concluíram que não faria sentido tratar a classe String como um caso especial e fornecer algo diferente de todos os bits zero como uma inicialização, mesmo que fosse algo como String.Emptyou "".
DJV
@ DanV: Alterar o comportamento de inicialização dos stringlocais de armazenamento exigiria também a alteração do comportamento de inicialização de todas as estruturas ou classes que possuem campos do tipo string. Isso representaria uma mudança muito grande no design do .net, que atualmente espera inicializar zero qualquer tipo sem precisar pensar no que é, exceto apenas pelo tamanho total.
Supercat
2

Como string é um tipo de referência e o valor padrão para o tipo de referência é nulo.

Akshay
fonte
0

Talvez a stringpalavra - chave tenha confundido você, pois se parece exatamente com qualquer outra declaração de tipo de valor , mas na verdade é um alias, System.Stringconforme explicado nesta pergunta .
Além disso, a cor azul escura no Visual Studio e a primeira letra minúscula podem levar a pensar que é a struct.

Alessandro Da Rugna
fonte
3
Não é o mesmo com a objectpalavra - chave? Embora seja certo que isso é muito menos usado do que string.
2
Como inté um apelido para System.Int32. Onde você quer chegar? :)
Thorarin
@Thorari @delnan: Eles são ambos os aliases, mas System.Int32é um Struct, assim, ter um valor padrão, enquanto System.Stringé um Classtendo um ponteiro com valor padrão null. Eles são apresentados visualmente na mesma fonte / cor. Sem conhecimento, pode-se pensar que eles agem da mesma maneira (= tendo um valor padrão). Minha resposta foi escrito com uma en.wikipedia.org/wiki/Cognitive_psychology ideia psicologia cognitiva por trás dele :-)
Alessandro Da Rugna
Estou bastante certo de que Anders Hejlsberg disse isso em uma entrevista no canal 9. Eu sei a diferença entre pilha e pilha, mas a idéia com C # é que o programador casual não precisa.
21415 Thomas Koelle
0

Tipos anuláveis ​​não chegaram até a versão 2.0.

Se tipos anuláveis ​​tivessem sido criados no início do idioma, a string seria não anulável e a string? teria sido anulável. Mas eles não podiam fazer isso com compatibilidade com versões anteriores.

Muitas pessoas falam sobre ref-type ou não ref type, mas string é uma classe fora do comum e soluções teriam sido encontradas para tornar isso possível.

Thomas Koelle
fonte