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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
blindmeis
  • 22,175
  • 7
  • 55
  • 74

7 Answers7

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:

Pieter Geerkens
  • 11,775
  • 2
  • 32
  • 52
  • 4
    Thanks for the link. I also wrote about the semantics of nulls in VB / VBScript and JScript here: http://blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx and here: http://blogs.msdn.com/b/ericlippert/archive/2003/10/01/53128.aspx – Eric Lippert Mar 20 '13 at 15:34
  • 27
    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. – Eric Lippert Mar 20 '13 at 15:37
  • @EricLippert: Thank you; I wil check out those additional links. – Pieter Geerkens Mar 20 '13 at 15:37
  • 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? – BlueRaja - Danny Pflughoeft Mar 20 '13 at 17:47
  • 2
    @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?`. – Servy Mar 20 '13 at 18:06
  • 1
    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`? – Ben Voigt Mar 02 '14 at 20:53
  • 1
    @BenVoigt: Thank you. All those up-votes and you are the first to spot that typo. ;-) – Pieter Geerkens Mar 02 '14 at 22:09
  • "*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`. – Heinzi Oct 18 '18 at 11:51
  • @EricLippert Where did your semantics blog articles go? – Michel de Ruiter Jun 18 '20 at 16:46
  • 1
    @MicheldeRuiter: I've updated the answer with the new location of those links. – Pieter Geerkens Jun 18 '20 at 17:30
  • 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... – Zeus Sep 01 '21 at 00:43
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) '
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • 16
    "VB.NET Nothing <> C# null" does it return true for C# and false for VB.Net? Just kidding :-p – ken2k Mar 20 '13 at 12:59
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).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
nothrow
  • 15,882
  • 9
  • 57
  • 104
  • Why answer the question by trial and error? Should be possible to do it from the language specifications. – David Heffernan Mar 20 '13 at 12:59
  • 3
    @DavidHeffernan, because this shows the difference in language that is pretty unambiguous. – nothrow Mar 20 '13 at 13:01
  • 2
    @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. – Servy Mar 20 '13 at 14:10
  • 2
    @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. – Eric Lippert Mar 20 '13 at 15:39
  • @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. – nothrow Mar 20 '13 at 16:45
  • 2
    @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). – Servy Mar 20 '13 at 16:50
  • @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! – David Heffernan Mar 20 '13 at 17:15
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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
supercat
  • 77,689
  • 9
  • 166
  • 211
  • 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. – ErikE Aug 23 '16 at 06:07
  • @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). – supercat Aug 23 '16 at 14:39
  • In generics you can get tricky problems around nullable types and overload resolution... – ErikE Aug 23 '16 at 14:45
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.

Community
  • 1
  • 1
evgenyl
  • 7,837
  • 2
  • 27
  • 32
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
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 5
    It's "weirdness" when it's not familiar. See answer given by Pieter Geerkens. – rskar Mar 20 '13 at 13:05
  • 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). – Matthew Watson Mar 20 '13 at 14:40
  • 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 http://stackoverflow.com/questions/14837209/why-c-sharp-fails-to-compare-two-object-types-with-each-other-but-vb-doesnt and http://stackoverflow.com/questions/10176737/object-equality-behaves-different-in-net – rskar Mar 20 '13 at 15:33
  • 1
    @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.) – Servy Mar 20 '13 at 18:11
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#.

Dave Doknjas
  • 6,394
  • 1
  • 15
  • 28
  • 1
    Unless `x` is `nothing`, in which case `x.Equals(y)` will throw an exception. – Servy Mar 20 '13 at 21:24
  • @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. – Dave Doknjas Sep 14 '18 at 15:49
  • Specifically, a nullable instance initialized to 'null' is not really a variable set to null, but a System.Nullable instance with no value set. – Dave Doknjas Sep 14 '18 at 16:08