Why is there a difference in checking null against a value in VB.NET and C#?
Asked Answered
D

7

114

In VB.NET this happens:

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

But in C# this happens:

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

Why is there a difference?

Deforest answered 20/3, 2013 at 12:50 Comment(12)
that's terrifying.Memling
I believe default(decimal?) is returning 0, not null.Colon
@RyanFrame NO. Since it is nullable types, it returns nullKhasi
@RyanFrame Even if that were the case, 0 != 5 just as null != 5, right?Hereto
Yep... so I have no ideas.Colon
possible duplicate of C# vs VB.NET - Handling of null StructuresKhasi
x is null in c# and nothing in vb - thats why i dont get the different behaviorDeforest
Oh yeah... right... in VB If conditionals do not require to evaluate as a boolean... uuuugh EDIT: So Nothing <> Anything = Nothing which results in the If taking the negative/else route.Hereto
@Default Because I had always just assumed that null and nothing were equivalent, thought this was a clever way of pointing out how this is not the case, hence the +1Rookie
@JMK: Null, Nothing and Empty are actually all subtly different. If they were all the same then you wouldn't need three of them.Gauguin
@EricLippert Very interesting, your blog posts on the topic are great too, thanks!Rookie
Cripes, VB terrifies me sometimes...Jelle
G
92

VB.NET and C#.NET are different languages, built by different teams who have made different assumptions about usage; in this case the semantics of a NULL comparison.

My personal preference is for the VB.NET semantics, which in essence gives NULL the semantics "I don't know yet". Then the comparison of 5 to "I don't know yet". is naturally "I don't know yet"; ie NULL. This has the additional advantage of mirroring the behaviour of NULL in (most if not all) SQL databases. This is also a more standard (than C#'s) interpretation of three-valued logic, as explained here.

The C# team made different assumptions about what NULL means, resulting in the behaviour difference you show. Eric Lippert wrote a blog about the meaning of NULL in C#. Per Eric Lippert: "I also wrote about the semantics of nulls in VB / VBScript and JScript here and here".

In any environment in which NULL values are possible, it is imprtant to recognize that the Law of the Excluded Middle (ie that A or ~A is tautologically true) no longer can be relied on.

Update:

A bool (as opposed to a bool?) can only take the values TRUE and FALSE. However a language implementation of NULL must decide on how NULL propagates through expressions. In VB the expressions 5=null and 5<>null BOTH return false. In C#, of the comparable expressions 5==null and 5!=null only the second first [updated 2014-03-02 - PG] returns false. However, in ANY environment that supports null, it is incumbent on the programmer to know the truth tables and null-propagation used by that language.

Update

Eric Lippert's blog articles (mentioned in his comments below) on semantics are now at:

Goran answered 20/3, 2013 at 12:56 Comment(11)
Thanks for the link. I also wrote about the semantics of nulls in VB / VBScript and JScript here: blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx and here: blogs.msdn.com/b/ericlippert/archive/2003/10/01/53128.aspxGauguin
And FYI the decision to make C# incompatible with VB in this way was a controversial one. I was not on the language design team at the time but the amount of debate that went into this decision was considerable.Gauguin
@EricLippert: Thank you; I wil check out those additional links.Goran
Wait, can a boolean expression ever return null in C#? If not, why do you say C# has an "interpretation of three-valued logic" at all, or claim that the law of excluded middle cannot be relied upon in a language that has null values?Impower
@BlueRaja-DannyPflughoeft In C# bool cannot have 3 values, only two. It's bool? that can have three values. operator == and operator != both return bool, not bool?, regardless of the type of the operands. Additionally, an if statement can only accept a bool, not a bool?.Especial
In C# the expressions 5=null and 5<>null aren't valid. And of 5 == null and 5 != null, are you sure it is the second that returns false?Depth
@BenVoigt: Thank you. All those up-votes and you are the first to spot that typo. ;-)Goran
"In VB the expressions 5=null and 5<>null BOTH return false." Not exactly. If you try Dim b = (5 = new Int32?()), you will see that the inferred type of b is Boolean? with HasValue = false. In other words, the two expressions return "null", not "false". It's just that VB.NET's If statement (in contrast to C#'s if statement) also accepts a Boolean? as the condition and treats it as False if it is Nothing.Laughlin
@EricLippert Where did your semantics blog articles go?Joanne
@MicheldeRuiter: I've updated the answer with the new location of those links.Goran
It is perhaps more useful (for most people) to draw a parallel with the floating point NaN (rather that with SQL rules). Anyone dealing with floating point (and that's nearly every programmer) must know that no matter what and how you compare with NaN, you get false. Same with Nothing. Except that VB has a terrible fault of equating Nothing with empty string...Hengel
N
37

Because x <> y returns Nothing instead of true. It is simply not defined since x is not defined. (similar to SQL null).

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

You also have to compare the value of a Nullable(Of Decimal) only if it has a value.

So the VB.NET above compares similar to this(which looks less incorrect):

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

The VB.NET language specification:

7.1.1 Nullable Value Types ... A nullable value type can contain the same values as the non-nullable version of the type as well as the null value. Thus, for a nullable value type, assigning Nothing to a variable of the type sets the value of the variable to the null value, not the zero value of the value type.

For example:

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) '
Nebula answered 20/3, 2013 at 12:55 Comment(1)
"VB.NET Nothing <> C# null" does it return true for C# and false for VB.Net? Just kidding :-pPathos
P
17

Look at the generated CIL (I've converted both to 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");
    }
}

You'll see that the comparison in Visual Basic returns Nullable<bool> (not bool, false or true!). And undefined converted to bool is false.

Nothing compared to whatever is always Nothing, not false in Visual Basic (it is the same as in SQL).

Placard answered 20/3, 2013 at 12:58 Comment(7)
Why answer the question by trial and error? Should be possible to do it from the language specifications.Icy
@DavidHeffernan, because this shows the difference in language that is pretty unambiguous.Placard
@Yossarian You think the language specs are ambiguous on the issue. I disagree. The IL is an implementation detail subject to change; the specs are not.Especial
@DavidHeffernan: I like your attitude and encourage you to try. The VB language specification can be difficult to parse at times. Lucian has been improving it for some years now but it can still be quite difficult to suss out the exact meanings of these sorts of corner cases. I suggest that you obtain a copy of the spec, do some research, and report your findings.Gauguin
@Servy, I must concur, IL isn't subject to change, as it is well-specified (and, I'd say, that it is more understandable than some features). It may only evolve.Placard
@Yossarian The results of executing the IL code you've provided is not subject to change, but that the C#/VB code provided will be compiled into the IL code you showed is subject to change (as long as the behavior of that IL is also inline with the language specs' definition).Especial
@EricLippert I tried. But I failed. It's more than plausible that I don't know how to read specification documents. They are not the easiest things to read!Icy
S
6

The problem that's observed here is a special case of a more general problem, which is that the number of different definitions of equality that may be useful in at least some circumstances exceeds the number of commonly-available means to express them. This problem is in some cases made worse by an unfortunate belief that it is confusing to have different means of testing equality yield different results, and such confusion might be avoided by having the different forms of equality yield the same results whenever possible.

In reality, the fundamental cause of confusion is a misguided belief that the different forms of equality and inequality testing should be expected to yield the same result, notwithstanding the fact that different semantics are useful in different circumstances. For example, from an arithmetic standpoint, it's useful to be able to have Decimal which differ only in the number of trailing zeroes compare as equal. Likewise for double values like positive zero and negative zero. On the other hand, from a caching or interning standpoint, such semantics can be deadly. Suppose, for example, one had a Dictionary<Decimal, String> such that myDict[someDecimal] should equal someDecimal.ToString(). Such an object would seem reasonable if one had many Decimal values that one wanted to convert to string and expected there to be many duplicates. Unfortunately, if used such caching to convert 12.3 m and 12.40 m, followed by 12.30 m and 12.4 m, the latter values would yield "12.3", and "12.40" instead of "12.30" and "12.4".

Returning to the matter at hand, there is more than one sensible way of comparing nullable objects for equality. C# takes the standpoint that its == operator should mirror the behavior of Equals. VB.NET takes the standpoint that its behavior should mirror that of some other languages, since anyone who wants the Equals behavior could use Equals. In some sense, the right solution would be to have a three-way "if" construct, and require that if the conditional expression returns a three-valued result, code must specify what should happen in the null case. Since that is not an option with languages as they are, the next best alternative is to simply learn how different languages work and recognize that they are not the same.

Incidentally, Visual Basic's "Is" operator, which is lacking in C, can be used to test for whether a nullable object is, in fact, null. While one might reasonably question whether an if test should accept a Boolean?, having the normal comparison operators return Boolean? rather than Boolean when invoked on nullable types is a useful feature. Incidentally, in VB.NET, if one attempts to use the equality operator rather than Is, one will get a warning that the result of the comparison will always be Nothing, and one should use Is if one wants to test if something is null.

Sean answered 20/3, 2013 at 20:47 Comment(3)
Testing whether a class is null in C# is done by == null. And testing whether a nullable value type has a value is done by .hasValue. What use is there for an Is Nothing operator? C# does have is but it tests for type compatibility. In light of these, I really am not sure what your last paragraph is trying to say.Blocky
@ErikE: Both vb.net and C# allow nullable types to be checked for a value using a comparison to null, though both languages treat that as syntactic sugar for a HasValue check, at least in cases where the type is known (I'm not sure what code is generated for generics).Sean
In generics you can get tricky problems around nullable types and overload resolution...Blocky
T
3

May be this post well help you:

If I remember correctly, 'Nothing' in VB means "the default value". For a value type, that's the default value, for a reference type, that would be null. Thus, assigning nothing to a struct, is no problem at all.

Teletypesetter answered 20/3, 2013 at 12:55 Comment(2)
This doesn't answer the question.Icy
No, it clarifies nothing. The question is all about the <> operator in VB, and how it operates on nullable types.Icy
C
2

This is a definite weirdness of VB.

In VB, if you want to compare two nullable types, you should use Nullable.Equals().

In your example, it should be:

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
Cremona answered 20/3, 2013 at 12:59 Comment(4)
It's "weirdness" when it's not familiar. See answer given by Pieter Geerkens.Winnah
Well I also think it strange that VB doesn't reproduce the behaviour of Nullable<>.Equals(). One might expect it to work the same way (which is what C# does).Cremona
Expectations, as in what "one might expect," are about what one has experienced. C# was designed with the expectations of Java users in mind. Java was designed with the expectations of C/C++ users in mind. For better or worse, VB.NET was designed with the expectations of VB6 users in mind. More food for thought at #14837709 and #10177237Winnah
@MatthewWatson The definition of Nullable didn't exist in the first versions of .NET, it was created after C# and VB.NET had been out for some time and already determined their null propagation behavior. Do you honestly expect the language to have been consistent with a type that won't have been created for several years? From the point of view of a VB.NET programmer, it's Nullable.Equals that's not consistent with the language, rather than the other way around. (Given that C# and VB both use the same Nullable definition, there was no way for it to be consistent with both languages.)Especial
P
0

Your VB code is simply incorrect - if you change the "x <> y" to "x = y" you will still have "false" as the result. The most common way of expression this for nullable instances is "Not x.Equals(y)", and this will yield the same behavior as "x != y" in C#.

Philosophical answered 20/3, 2013 at 15:44 Comment(3)
Unless x is nothing, in which case x.Equals(y) will throw an exception.Especial
@Servy: Stumbled upon this again (many years later), and noticed that I didn't correct you - "x.Equals(y)" will not throw an exception for nullable type instance 'x'. Nullable types are treated differently by the compiler.Philosophical
Specifically, a nullable instance initialized to 'null' is not really a variable set to null, but a System.Nullable instance with no value set.Philosophical

© 2022 - 2024 — McMap. All rights reserved.