Por que há uma diferença em verificar nulo em relação a um valor em VB.NET e C #?

110

Em VB.NET isso acontece:

Dim x As System.Nullable(Of Decimal) = Nothing
Dim y As System.Nullable(Of Decimal) = Nothing

y = 5
If x <> y Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false") '' <-- I got this. Why?
End If

Mas em C # isso acontece:

decimal? x = default(decimal?);
decimal? y = default(decimal?);

y = 5;
if (x != y)
{
    Debug.WriteLine("true"); // <-- I got this -- I'm with you, C# :)
}
else
{
    Debug.WriteLine("false");
}

Por que existe uma diferença?

blindmeis
fonte
22
isso é assustador.
Mikeb de
8
Eu acredito que default(decimal?)está retornando 0, não null.
Ryan Frame
7
@RyanFrame NO. Como são tipos anuláveis , ele retornanull
Soner Gönül
4
Ah sim ... certo ... em VB Ifcondicionais não precisam ser avaliados como booleanos ... uuuugh EDIT: Então, o Nothing <> Anything = Nothingque resulta na escolha da Ifrota negativa / else.
Chris Sinclair
13
@JMK: Nulo, Nada e Vazio são, na verdade, todos sutilmente diferentes. Se eles fossem todos iguais, você não precisaria de três.
Eric Lippert de

Respostas:

88

VB.NET e C # .NET são linguagens diferentes, construídas por equipes diferentes que fizeram suposições diferentes sobre o uso; neste caso, a semântica de uma comparação NULL.

Minha preferência pessoal é pela semântica VB.NET, que em essência dá a NULL a semântica "Eu não sei ainda". Em seguida, a comparação de 5 com "Não sei ainda". é naturalmente "Não sei ainda"; ou seja, NULL. Isso tem a vantagem adicional de espelhar o comportamento de NULL na maioria dos bancos de dados SQL (se não em todos). Esta também é uma interpretação mais padrão (do que C #) da lógica de três valores, conforme explicado aqui .

A equipe C # fez diferentes suposições sobre o que NULL significa, resultando na diferença de comportamento que você mostra. Eric Lippert escreveu um blog sobre o significado de NULL em C # . Por Eric Lippert: "Eu também escrevi sobre a semântica de nulos em VB / VBScript e JScript aqui e aqui ".

Em qualquer ambiente no qual valores NULL são possíveis, é importante reconhecer que a Lei do Meio Excluído (isto é, que A ou ~ A é tautologicamente verdadeiro) não pode mais ser confiável.

Atualizar:

A bool(ao contrário de a bool?) só pode assumir os valores TRUE e FALSE. No entanto, uma implementação de linguagem de NULL deve decidir como NULL se propaga por meio de expressões. Em VB, as expressões 5=nulle 5<>nullAMBOS retornam falso. Em C #, das expressões comparáveis 5==nulle 5!=nullapenas a segunda primeira [atualizado em 02/03/2014 - PG] retorna falso. Entretanto, em QUALQUER ambiente que suporte nulo, é responsabilidade do programador saber as tabelas de verdade e propagação de nulos usadas por aquela linguagem.

Atualizar

Os artigos do blog de Eric Lippert (mencionados em seus comentários abaixo) sobre semântica estão agora em:

Pieter Geerkens
fonte
4
Obrigado pelo link. Também escrevi sobre a semântica de nulos em VB / VBScript e JScript aqui: blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx e aqui: blogs.msdn.com/b/ericlippert/ arquivo / 2003/10/01 / 53128.aspx
Eric Lippert
27
E para sua informação, a decisão de tornar o C # incompatível com o VB dessa forma foi controversa. Eu não fazia parte da equipe de design de linguagem na época, mas a quantidade de debate que envolveu essa decisão foi considerável.
Eric Lippert de
2
@ BlueRaja-DannyPflughoeft Em C # boolnão pode ter 3 valores, apenas dois. É bool?que pode ter três valores. operator ==e operator !=ambos retornam bool, não bool?, independente do tipo dos operandos. Além disso, uma ifdeclaração só pode aceitar a bool, não a bool?.
Servy de
1
Em C # as expressões 5=nulle 5<>nullnão são válidas. E de 5 == nulle 5 != null, tem certeza de que é o segundo que volta false?
Ben Voigt,
1
@BenVoigt: Obrigado. Todos aqueles votos positivos e você é o primeiro a detectar esse erro de digitação. ;-)
Pieter Geerkens
37

Porque x <> yretorna em Nothingvez de true. Simplesmente não está definido, pois xnão está definido. (semelhante a SQL null).

Nota: VB.NET Nothing<> C # null.

Você também deve comparar o valor de a Nullable(Of Decimal)apenas se ele tiver um valor.

Portanto, o VB.NET acima se compara a este (que parece menos incorreto):

If x.HasValue AndAlso y.HasValue AndAlso x <> y Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false")  
End If

A especificação da linguagem VB.NET :

7.1.1 Tipos de valor anulável ... Um tipo de valor anulável pode conter os mesmos valores que a versão não anulável do tipo, bem como o valor nulo. Portanto, para um tipo de valor anulável, atribuir Nothing a uma variável do tipo define o valor da variável como o valor nulo, não o valor zero do tipo de valor.

Por exemplo:

Dim x As Integer = Nothing
Dim y As Integer? = Nothing

Console.WriteLine(x) ' Prints zero '
Console.WriteLine(y) ' Prints nothing (because the value of y is the null value) '
Tim Schmelter
fonte
16
"VB.NET Nothing <> C # null" retorna verdadeiro para C # e falso para VB.Net? Brincadeira :-p
ken2k
17

Observe o CIL gerado (converti ambos para C #):

C #:

private static void Main(string[] args)
{
    decimal? x = null;
    decimal? y = null;
    y = 5M;
    decimal? CS$0$0000 = x;
    decimal? CS$0$0001 = y;
    if ((CS$0$0000.GetValueOrDefault() != CS$0$0001.GetValueOrDefault()) ||
        (CS$0$0000.HasValue != CS$0$0001.HasValue))
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}

Visual básico:

[STAThread]
public static void Main()
{
    decimal? x = null;
    decimal? y = null;
    y = 5M;
    bool? VB$LW$t_struct$S3 = new bool?(decimal.Compare(x.GetValueOrDefault(), y.GetValueOrDefault()) != 0);
    bool? VB$LW$t_struct$S1 = (x.HasValue & y.HasValue) ? VB$LW$t_struct$S3 : null;
    if (VB$LW$t_struct$S1.GetValueOrDefault())
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}

Você verá que a comparação no Visual Basic retorna Nullable <bool> (não bool, false ou true!). E indefinido convertido em bool é falso.

Nothingcomparado a tudo o que é sempre Nothing, não falso no Visual Basic (é o mesmo que no SQL).

nothrow
fonte
Por que responder à pergunta por tentativa e erro? Deve ser possível fazê-lo a partir das especificações do idioma.
David Heffernan
3
@DavidHeffernan, porque mostra a diferença de linguagem que é bastante inequívoca.
nothrow de
2
@Yossarian Você acha que as especificações do idioma são ambíguas sobre o assunto. Discordo. O IL é um detalhe de implementação sujeito a alterações; as especificações não são.
Servy de
2
@DavidHeffernan: Gosto da sua atitude e encorajo-o a experimentar. A especificação da linguagem VB pode ser difícil de analisar às vezes. Lucian o vem aprimorando há alguns anos, mas ainda pode ser muito difícil descobrir os significados exatos desses tipos de casos extremos. Eu sugiro que você obtenha uma cópia das especificações, faça algumas pesquisas e relate suas descobertas.
Eric Lippert de
2
@Yossarian Os resultados da execução do código IL que você forneceu não estão sujeitos a alterações, mas o código C # / VB fornecido será compilado no código IL que você mostrou e está sujeito a alterações (desde que o comportamento desse IL seja também em linha com a definição das especificações de idioma).
Servy de
6

O problema observado aqui é um caso especial de um problema mais geral, que é que o número de diferentes definições de igualdade que podem ser úteis em pelo menos algumas circunstâncias excede o número de meios comumente disponíveis para expressá-las. Em alguns casos, esse problema é agravado por uma crença infeliz de que é confuso ter diferentes meios de testar a igualdade produzindo resultados diferentes, e tal confusão pode ser evitada fazendo com que as diferentes formas de igualdade produzam os mesmos resultados sempre que possível.

Na realidade, a causa fundamental da confusão é uma crença equivocada de que se deve esperar que as diferentes formas de teste de igualdade e desigualdade produzam o mesmo resultado, não obstante o fato de que semânticas diferentes são úteis em circunstâncias diferentes. Por exemplo, do ponto de vista aritmético, é útil ser capaz de ter Decimalquais diferem apenas no número de zeros à direita comparados como iguais. Da mesma forma para doublevalores como zero positivo e zero negativo. Por outro lado, do ponto de vista de cache ou internamento, essa semântica pode ser mortal. Suponha, por exemplo, que alguém tenha um Dictionary<Decimal, String>tal que myDict[someDecimal]deve ser igual someDecimal.ToString(). Tal objeto pareceria razoável se alguém tivesse muitosDecimalvalores que se deseja converter em string e espera-se que haja muitas duplicatas. Infelizmente, se usado esse cache para converter 12,3 me 12,40 m, seguidos por 12,30 me 12,4 m, os últimos valores renderiam "12,3" e "12,40" em vez de "12,30" e "12,4".

Voltando ao assunto em questão, há mais de uma maneira sensata de comparar objetos anuláveis ​​para igualdade. C # assume o ponto de vista de que seu ==operador deve espelhar o comportamento de Equals. O VB.NET considera que seu comportamento deve espelhar o de algumas outras linguagens, já que quem quiser o Equalscomportamento pode usar Equals. Em certo sentido, a solução certa seria ter uma construção "se" de três vias e exigir que, se a expressão condicional retornar um resultado com três valores, o código deve especificar o que deve acontecer no nullcaso. Já que essa não é uma opção com as línguas como elas são, a próxima melhor alternativa é simplesmente aprender como diferentes línguas funcionam e reconhecer que não são iguais.

A propósito, o operador "Is" do Visual Basic, que está faltando em C, pode ser usado para testar se um objeto anulável é, de fato, nulo. Embora alguém possa questionar se um ifteste deve aceitar um Boolean?, fazer com que os operadores de comparação normais retornem em Boolean?vez de Booleanquando invocados em tipos anuláveis ​​é um recurso útil. A propósito, no VB.NET, se alguém tentar usar o operador de igualdade em vez de Is, obterá um aviso de que o resultado da comparação sempre será Nothing, e deve- Isse usar se quiser testar se algo é nulo.

supergato
fonte
Testar se uma classe é nula em C # é feito por == null. E testar se um tipo de valor anulável tem um valor é feito por .hasValue. Qual a utilidade de um Is Nothingoperador? C # tem, ismas testa a compatibilidade de tipo. À luz disso, realmente não tenho certeza do que seu último parágrafo está tentando dizer.
ErikE
@ErikE: Ambos vb.net e C # permitem que tipos anuláveis ​​sejam verificados para um valor usando uma comparação com null, embora ambas as linguagens tratem isso como um açúcar sintático para uma HasValueverificação, pelo menos nos casos em que o tipo é conhecido (não tenho certeza qual código é gerado para genéricos).
supercat
Em genéricos, você pode ter problemas complicados em torno de tipos anuláveis ​​e resolução de sobrecarga ...
ErikE
3

Pode ser que este post te ajude:

Se bem me lembro, 'Nada' em VB significa "o valor padrão". Para um tipo de valor, esse é o valor padrão, para um tipo de referência, que seria nulo. Portanto, não atribuir nada a uma estrutura não é problema algum.

evgenyl
fonte
3
Isso não responde à pergunta.
David Heffernan
Não, não esclarece nada. A questão é sobre o <>operador em VB e como ele opera em tipos anuláveis.
David Heffernan
2

Esta é uma estranheza definitiva do VB.

No VB, se você deseja comparar dois tipos anuláveis, você deve usar Nullable.Equals().

Em seu exemplo, deveria ser:

Dim x As System.Nullable(Of Decimal) = Nothing
Dim y As System.Nullable(Of Decimal) = Nothing

y = 5
If Not Nullable.Equals(x, y) Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false")
End If
Matthew Watson
fonte
5
É "esquisitice" quando não é familiar. Veja a resposta dada por Pieter Geerkens.
rskar
Bem, também acho estranho que o VB não reproduza o comportamento do Nullable<>.Equals(). Pode-se esperar que funcione da mesma maneira (que é o que C # faz).
Matthew Watson de
As expectativas, como "o que se poderia esperar", referem-se ao que experimentamos. C # foi projetado com as expectativas dos usuários Java em mente. Java foi projetado com as expectativas dos usuários de C / C ++ em mente. Para o bem ou para o mal, o VB.NET foi projetado com as expectativas dos usuários do VB6 em mente. Mais ideias para reflexão em stackoverflow.com/questions/14837209/… e stackoverflow.com/questions/10176737/…
rskar
1
@MatthewWatson A definição de Nullablenão existia nas primeiras versões do .NET, ela foi criada depois que o C # e o VB.NET já estavam fora de uso há algum tempo e já determinavam seu comportamento de propagação nula. Você honestamente espera que a linguagem seja consistente com um tipo que não será criado por muitos anos? Do ponto de vista de um programador VB.NET, é Nullable.Equals que não é consistente com a linguagem, ao invés do contrário. (Dado que C # e VB usam a mesma Nullabledefinição, não havia como ser consistente com as duas linguagens.)
Servy de
0

Seu código VB está simplesmente incorreto - se você alterar "x <> y" para "x = y", ainda terá "false" como resultado. A forma mais comum de expressão para instâncias anuláveis ​​é "Not x.Equals (y)", e isso produzirá o mesmo comportamento de "x! = Y" em C #.

Dave Doknjas
fonte
1
A menos que xseja nothing, nesse caso x.Equals(y)lançará uma exceção.
Servy de
@Servy: Tropecei nisso novamente (muitos anos depois) e percebi que não te corrigi - "x.Equals (y)" não lançará uma exceção para a instância de tipo anulável 'x'. Tipos anuláveis ​​são tratados de forma diferente pelo compilador.
Dave Doknjas
Especificamente, uma instância anulável inicializada como 'nula' não é realmente uma variável definida como nula, mas uma instância System.Nullable sem valor definido.
Dave Doknjas