¿Por qué hay una diferencia al comparar nulo con un valor en VB.NET y C #?

110

En VB.NET esto sucede:

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

Pero en C # sucede esto:

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 qué hay una diferencia?

ciego
fuente
22
eso es aterrador.
Mikeb
8
Creo que default(decimal?)devuelve 0, no null.
Ryan Frame
7
@RyanFrame NO. Dado que son tipos que null
aceptan valores
4
Oh, sí ... cierto ... en VB los Ifcondicionales no requieren evaluar como un booleano ... uuuugh EDIT: Entonces, lo Nothing <> Anything = Nothingque resulta en Iftomar la ruta negativa / else.
Chris Sinclair
13
@JMK: Nulo, Nada y Vacío son en realidad todos sutilmente diferentes. Si fueran todos iguales, no necesitarías tres de ellos.
Eric Lippert

Respuestas:

88

VB.NET y C # .NET son lenguajes diferentes, creados por diferentes equipos que han hecho diferentes suposiciones sobre el uso; en este caso la semántica de una comparación NULL.

Mi preferencia personal es por la semántica de VB.NET, que en esencia le da a NULL la semántica "No lo sé todavía". Luego, la comparación de 5 con "Todavía no lo sé". es naturalmente "no lo sé todavía"; es decir, NULO. Esto tiene la ventaja adicional de reflejar el comportamiento de NULL en (la mayoría, si no todas) las bases de datos SQL. Esta es también una interpretación más estándar (que la de C #) de la lógica de tres valores, como se explica aquí .

El equipo de C # hizo diferentes suposiciones sobre lo que significa NULL, lo que resultó en la diferencia de comportamiento que muestra. Eric Lippert escribió un blog sobre el significado de NULL en C # . Según Eric Lippert: "También escribí sobre la semántica de nulos en VB / VBScript y JScript aquí y aquí ".

En cualquier entorno en el que sean posibles valores NULL, es importante reconocer que ya no se puede confiar en la Ley del Medio Excluido (es decir, que A o ~ A es tautológicamente verdadera).

Actualizar:

A bool(a diferencia de a bool?) solo puede tomar los valores VERDADERO y FALSO. Sin embargo, una implementación de lenguaje de NULL debe decidir cómo se propaga NULL a través de expresiones. En VB, las expresiones 5=nully 5<>nullAMBAS devuelven falso. En C #, de las expresiones comparables 5==nully 5!=nullsolo la segunda primera [actualizado 2014-03-02 - PG] devuelve falso. Sin embargo, en CUALQUIER entorno que admita nulos, es responsabilidad del programador conocer las tablas de verdad y la propagación de nulos utilizadas por ese lenguaje.

Actualizar

Los artículos del blog de Eric Lippert (mencionados en sus comentarios a continuación) sobre semántica están ahora en:

Pieter Geerkens
fuente
4
Gracias por el enlace. También escribí sobre la semántica de nulos en VB / VBScript y JScript aquí: blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx y aquí: blogs.msdn.com/b/ericlippert/ archive / 2003/10/01 / 53128.aspx
Eric Lippert
27
Y para su información, la decisión de hacer C # incompatible con VB de esta manera fue controvertida. No formaba parte del equipo de diseño del lenguaje en ese momento, pero la cantidad de debate que se generó en esta decisión fue considerable.
Eric Lippert
2
@ BlueRaja-DannyPflughoeft En C # boolno se pueden tener 3 valores, solo dos. Es bool?que puede tener tres valores. operator ==y operator !=ambos devuelven bool, no bool?, independientemente del tipo de operandos. Además, una ifdeclaración solo puede aceptar un bool, no un bool?.
Servicio
1
En C # las expresiones 5=nully 5<>nullno son válidas. Y de 5 == nully 5 != null, ¿estás seguro de que es el segundo que vuelve false?
Ben Voigt
1
@BenVoigt: Gracias. Todos esos votos a favor y usted es el primero en detectar ese error tipográfico. ;-)
Pieter Geerkens
37

Porque x <> ydevuelve en Nothinglugar de true. Simplemente no está definido ya xque no está definido. (similar a SQL nulo).

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

También debe comparar el valor de a Nullable(Of Decimal)solo si tiene un valor.

Entonces, el VB.NET anterior se compara de manera similar a esto (que parece menos incorrecto):

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

La especificación del lenguaje VB.NET :

7.1.1 Tipos de valor que aceptan valores NULL ... Un tipo de valor que admite valores NULL puede contener los mismos valores que la versión del tipo que no admite valores NULL, así como el valor nulo. Por lo tanto, para un tipo de valor que acepta valores NULL, asignar Nothing a una variable del tipo establece el valor de la variable en el valor nulo, no el valor cero del tipo de valor.

Por ejemplo:

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
fuente
16
"VB.NET Nothing <> C # null" ¿devuelve verdadero para C # y falso para VB.Net? Es broma :-p
ken2k
17

Mira el CIL generado (he convertido ambos a 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 Basic:

[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");
    }
}

Verá que la comparación en Visual Basic devuelve Nullable <bool> (¡no bool, falso o verdadero!). Y undefined convertido a bool es falso.

Nothingcomparado con lo que sea siempre Nothing, no falso en Visual Basic (es lo mismo que en SQL).

nothrow
fuente
¿Por qué responder a la pregunta por ensayo y error? Debería ser posible hacerlo a partir de las especificaciones del idioma.
David Heffernan
3
@DavidHeffernan, porque esto muestra la diferencia en el lenguaje que es bastante inequívoca.
nothrow
2
@Yossarian Crees que las especificaciones de idioma son ambiguas sobre el tema. Estoy en desacuerdo. El IL es un detalle de implementación sujeto a cambios; las especificaciones no lo son.
Servicio
2
@DavidHeffernan: Me gusta tu actitud y te animo a que lo pruebes. A veces, la especificación del lenguaje VB puede ser difícil de analizar. Lucian lo ha estado mejorando durante algunos años, pero aún puede ser bastante difícil descubrir el significado exacto de este tipo de casos de esquina. Le sugiero que obtenga una copia de la especificación, investigue un poco e informe sus hallazgos.
Eric Lippert
2
@Yossarian Los resultados de la ejecución del código IL que ha proporcionado no están sujetos a cambios, pero el código C # / VB proporcionado se compilará en el código IL que mostró está sujeto a cambios (siempre que el comportamiento de ese IL sea también en línea con la definición de las especificaciones del idioma).
Servicio
6

El problema que se observa aquí es un caso especial de un problema más general, que es que el número de diferentes definiciones de igualdad que pueden ser útiles en al menos algunas circunstancias excede el número de medios comúnmente disponibles para expresarlas. En algunos casos, este problema se agrava por la desafortunada creencia de que es confuso que diferentes medios para probar la igualdad den resultados diferentes, y tal confusión podría evitarse haciendo que las diferentes formas de igualdad produzcan los mismos resultados siempre que sea posible.

En realidad, la causa fundamental de confusión es una creencia equivocada de que se debe esperar que las diferentes formas de prueba de igualdad y desigualdad produzcan el mismo resultado, a pesar del hecho de que diferentes semánticas son útiles en diferentes circunstancias. Por ejemplo, desde un punto de vista aritmético, es útil poder hacer que los Decimalque difieren solo en el número de ceros finales se comparen como iguales. Lo mismo ocurre con doublevalores como cero positivo y cero negativo. Por otro lado, desde el punto de vista del almacenamiento en caché o internación, esta semántica puede ser mortal. Supongamos, por ejemplo, que uno tuviera un Dictionary<Decimal, String>tal que myDict[someDecimal]debería ser igual someDecimal.ToString(). Tal objeto parecería razonable si uno tuviera muchosDecimalvalores que uno quería convertir en cadena y esperaba que hubiera muchos duplicados. Desafortunadamente, si se utiliza este almacenamiento en caché para convertir 12,3 my 12,40 m, seguidos de 12,30 my 12,4 m, estos últimos valores producirían "12,3" y "12,40" en lugar de "12,30" y "12,4".

Volviendo al tema que nos ocupa, hay más de una forma sensata de comparar la igualdad de objetos que aceptan valores NULL. C # adopta el punto de vista de que su ==operador debería reflejar el comportamiento de Equals. VB.NET adopta el punto de vista de que su comportamiento debería reflejar el de algunos otros lenguajes, ya que cualquiera que desee el Equalscomportamiento podría utilizar Equals. En cierto sentido, la solución correcta sería tener una construcción "si" de tres vías y requerir que si la expresión condicional devuelve un resultado de tres valores, el código debe especificar qué debería suceder en el nullcaso. Dado que esa no es una opción con los idiomas como son, la siguiente mejor alternativa es simplemente aprender cómo funcionan los diferentes idiomas y reconocer que no son iguales.

Por cierto, el operador "Is" de Visual Basic, que carece de C, se puede usar para probar si un objeto que acepta valores NULL es, de hecho, nulo. Si bien uno podría cuestionar razonablemente si una ifprueba debería aceptar a Boolean?, es una característica útil que los operadores de comparación normales regresen en Boolean?lugar de Booleancuando se invocan en tipos que aceptan valores NULL. Por cierto, en VB.NET, si uno intenta usar el operador de igualdad en lugar de Is, recibirá una advertencia de que el resultado de la comparación siempre será Nothing, y debe usarlo Issi desea probar si algo es nulo.

Super gato
fuente
La prueba de si una clase es nula en C # se realiza mediante == null. Y probar si un tipo de valor que acepta valores NULL tiene un valor se realiza mediante .hasValue. ¿De qué sirve un Is Nothingoperador? C # tiene, ispero prueba la compatibilidad de tipos. A la luz de estos, realmente no estoy seguro de lo que intenta decir su último párrafo.
ErikE
@ErikE: tanto vb.net como C # permiten que se verifique un valor de los tipos que aceptan valores NULL mediante una comparación null, aunque ambos lenguajes lo tratan como azúcar sintáctico para una HasValueverificación, al menos en los casos en que se conoce el tipo (no estoy seguro qué código se genera para los genéricos).
supercat
En genéricos, puede tener problemas complicados con los tipos que
aceptan valores
3

Puede ser que esta publicación te ayude:

Si mal no recuerdo, "Nada" en VB significa "el valor predeterminado". Para un tipo de valor, ese es el valor predeterminado, para un tipo de referencia, sería nulo. Por tanto, no asignar nada a una estructura no supone ningún problema.

evgenyl
fuente
3
Esto no responde a la pregunta.
David Heffernan
No, no aclara nada. La pregunta tiene que ver con el <>operador en VB y cómo opera en tipos que aceptan valores NULL.
David Heffernan
2

Esta es una rareza definida de VB.

En VB, si desea comparar dos tipos que aceptan valores NULL, debe usar Nullable.Equals().

En su ejemplo, debería 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
fuente
5
Es "rareza" cuando no es familiar. Vea la respuesta dada por Pieter Geerkens.
rskar
Bueno, también me parece extraño que VB no reproduzca el comportamiento de Nullable<>.Equals(). Uno podría esperar que funcione de la misma manera (que es lo que hace C #).
Matthew Watson
Las expectativas, como en lo que "uno podría esperar", se refieren a lo que uno ha experimentado. C # se diseñó teniendo en cuenta las expectativas de los usuarios de Java. Java fue diseñado teniendo en cuenta las expectativas de los usuarios de C / C ++. Para bien o para mal, VB.NET fue diseñado teniendo en cuenta las expectativas de los usuarios de VB6. Más elementos en los que reflexionar en stackoverflow.com/questions/14837209/… y stackoverflow.com/questions/10176737/…
rskar
1
@MatthewWatson La definición de Nullableno existía en las primeras versiones de .NET, se creó después de que C # y VB.NET estuvieron fuera durante algún tiempo y ya determinaron su comportamiento de propagación nula. ¿Honestamente espera que el lenguaje haya sido consistente con un tipo que no se habrá creado durante varios años? Desde el punto de vista de un programador de VB.NET, es Nullable. Equals que no es consistente con el lenguaje, y no al revés. (Dado que C # y VB usan la misma Nullabledefinición, no había forma de que fuera consistente con ambos lenguajes).
Servy
0

Su código VB es simplemente incorrecto - si cambia "x <> y" a "x = y" todavía tendrá "falso" como resultado. La forma más común de expresar esto para instancias que aceptan valores NULL es "Not x.Equals (y)", y esto producirá el mismo comportamiento que "x! = Y" en C #.

Dave Doknjas
fuente
1
A menos que xsea nothing, en cuyo caso x.Equals(y)se lanzará una excepción.
Servicio
@Servy: Tropecé con esto nuevamente (muchos años después), y noté que no te corrigí - "x.Equals (y)" no lanzará una excepción para la instancia de tipo anulable 'x'. Los tipos que aceptan valores NULL son tratados de manera diferente por el compilador.
Dave Doknjas
Específicamente, una instancia anulable inicializada en 'null' no es realmente una variable establecida en null, sino una instancia System.Nullable sin ningún valor establecido.
Dave Doknjas