89

I ran into this today and have no idea why the C# compiler isn't throwing an error.

Int32 x = 1;
if (x == null)
{
    Console.WriteLine("What the?");
}

I'm confused as to how x could ever possibly be null. Especially since this assignment definitely throws a compiler error:

Int32 x = null;

Is it possible that x could become null, did Microsoft just decide to not put this check into the compiler, or was it missed completely?

Update: After messing with the code to write this article, suddenly the compiler came up with a warning that the expression would never be true. Now I'm really lost. I put the object into a class and now the warning has gone away but left with the question, can a value type end up being null.

public class Test
{
    public DateTime ADate = DateTime.Now;

    public Test ()
    {
        Test test = new Test();
        if (test.ADate == null)
        {
            Console.WriteLine("What the?");
        }
    }
}
Joshua Belden
  • 10,273
  • 8
  • 40
  • 56
  • 9
    You can write `if (1 == 2)` as well. It's not the compiler's job to perform code path analysis; that's what static analysis tools and unit tests are for. – Aaronaught Dec 29 '09 at 00:26
  • For why the warning went away, see my answer; and no - it can't be a null. – Marc Gravell Dec 29 '09 at 00:27
  • 1
    Agreed on the (1 == 2), I was more wondering about the situation (1 == null) – Joshua Belden Dec 29 '09 at 15:59
  • Thanks everyone that responded. All making sense now. – Joshua Belden Dec 29 '09 at 16:00
  • 1
    Regarding the warning or no warning issue: If the struct in question is a so-called "simple type", like `int`, the compiler generates nice warnings. For the simple types the `==` operator is defined by the C# language specification. For other (not simple type) structs, the compiler **forgets** to emit a warning. See [Wrong compiler warning when comparing struct to null](http://stackoverflow.com/questions/13046115/) for details. For structs that are not simple types, the `==` operator must be overloaded by an `opeartor ==` method which is a member of the struct (otherwise no `==` is allowed). – Jeppe Stig Nielsen Jan 03 '13 at 13:29
  • possible duplicate of [How can an object not be compared to null?](http://stackoverflow.com/questions/648115/how-can-an-object-not-be-compared-to-null) – nawfal Apr 20 '13 at 05:52
  • @nawfal I think think this was gives the best answer of the `valuetype == null` weirdness. Perhaps this should be the targets of the dupes – Conrad Frix Apr 20 '13 at 06:33
  • @ConradFrix and this too http://stackoverflow.com/q/1225949/661933 is good, older too – nawfal Apr 20 '13 at 06:39
  • `Int32` isn't a value type. It's an `int` boxed up in an object. :) – Owen Johnson Feb 11 '19 at 17:37

11 Answers11

123

This is legal because operator overload resolution has a unique best operator to choose. There is an == operator that takes two nullable ints. The int local is convertible to a nullable int. The null literal is convertible to a nullable int. Therefore this is a legal usage of the == operator, and will always result in false.

Similarly, we also allow you to say "if (x == 12.6)", which will also always be false. The int local is convertible to a double, the literal is convertible to a double, and obviously they will never be equal.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 4
    Re your comment: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=348850 – Marc Gravell Dec 29 '09 at 00:41
  • 1
    Then why is a non-primitive (i.e. user defined) value type also comparable to a null reference? Are all value types implicitly convertible to `Nullable` for the purposes of method overloading on `operator ==` and `operator !=`? – James Dunne Nov 03 '10 at 18:45
  • 5
    @James: (I retract my earlier erroneous comment, which I have deleted.) User-defined value types *which have a user-defined equality operator* defined also by default have a *lifted user-defined equality operator* generated for them. The lifted user-defined equality operator is applicable for the reason you state: all value types are implicitly convertible to their corresponding nullable type, as is the null literal. It is *not* the case that a user-defined value type that *lacks* a user-defined comparison operator is comparable to the null literal. – Eric Lippert Nov 03 '10 at 20:21
  • 1
    Aha, thank you Eric. That explains why if (x == null) is valid with our structs that have operator == and operator != defined on them. It is essentially lifted to `if ((X?)x == (X?)null)`. Can I somehow implement the null-lifted operators on our struct to throw a NotSupportedException to prevent developers from doing this erroneous check in the first place? Our more green developers appear incapable of reasoning about this type of logic, so I'd love to let the compiler teach them a lesson, as it were. :) – James Dunne Nov 03 '10 at 20:33
  • 3
    @James: Sure, you can implement your own operator == and operator != that take nullable structs. If those exist then the compiler will use them rather than generating them for you automatically. (And incidentally I regret that the warning for the meaningless lifted operator on non-nullable operands does not produce a warning; that's an error in the compiler that we have not gotten around to fixing.) – Eric Lippert Nov 03 '10 at 21:06
  • @Eric: I have done so, but retracted it since I cannot detect comparison against the null literal distinct from a nullable value type set to a null reference. Yes, the warning would be nice; pity it went away. I did attempt a `static bool operator == (SomeID a, SomeID? b) { if (b == null) throw new NotSupportedException("Do not compare strongly-typed identifiers to nullable strongly-typed identifiers."); else return a._value == b._value; }` but I was scared of what that might do to the existing codebase for comparisons against nullable value types which might actually hold values. – James Dunne Nov 04 '10 at 00:07
  • 2
    We want our warning! We deserve it. – Jeppe Stig Nielsen Jan 03 '13 at 13:33
  • 3
    @JamesDunne: What about defining a `static bool operator == (SomeID a, String b)` and tagging it with `Obsolete`? If the second operand is an untyped literal `null`, that would be a better match than any form that required use of lifted operators, but if it's a `SomeID?` which happens to equal `null`, the lifted operator would win. – supercat Jun 10 '13 at 16:15
  • 1
    This caused problems for me when I refactored a class into a struct. Comparisons against null need to be updated to compare with a sentinel value (Empty/default) instead. To easily detect all occurrences in the code I defined the == operator with all combinations of Type and Type?, and then applied an [Obsolete] attribute to them to trigger a compiler warning. Seemed to work pretty well, so I can fix all the broken code. Hope this helps someone! – MarkPflug Aug 03 '13 at 00:04
  • @supercat, ingenious. This still allows nullable usage to work, unlike Mark's otherwise useful suggestion. – yoyo Feb 22 '14 at 23:42
  • These olutions don't work against existing .NET structures (e.g. Color) – Sedat Kapanoglu Jul 21 '15 at 20:52
18

It isn't an error, as there is a (int?) conversion; it does generate a warning in the example given:

The result of the expression is always 'false' since a value of type 'int' is never equal to 'null' of type 'int?'

If you check the IL, you'll see that it completely removes the unreachable branch - it doesn't exist in a release build.

Note however that it doesn't generate this warning for custom structs with equality operators. It used to in 2.0, but not in the 3.0 compiler. The code is still removed (so it knows that the code is unreachable), but no warning is generated:

using System;

struct MyValue
{
    private readonly int value;
    public MyValue(int value) { this.value = value; }
    public static bool operator ==(MyValue x, MyValue y) {
        return x.value == y.value;
    }
    public static bool operator !=(MyValue x, MyValue y) {
        return x.value != y.value;
    }
}
class Program
{
    static void Main()
    {
        int i = 1;
        MyValue v = new MyValue(1);
        if (i == null) { Console.WriteLine("a"); } // warning
        if (v == null) { Console.WriteLine("a"); } // no warning
    }
}

With the IL (for Main) - note everything except the MyValue(1) (which could have side-effects) has been removed:

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] valuetype MyValue v)
    L_0000: ldc.i4.1 
    L_0001: stloc.0 
    L_0002: ldloca.s v
    L_0004: ldc.i4.1 
    L_0005: call instance void MyValue::.ctor(int32)
    L_000a: ret 
}

this is basically:

private static void Main()
{
    MyValue v = new MyValue(1);
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    Someone internally reported this to me recently as well. I do not know why we stopped producing that warning. We've entered it as a bug. – Eric Lippert Dec 29 '09 at 00:29
  • 1
    Here you go: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=348850 – Marc Gravell Dec 29 '09 at 00:38
5

The fact that a comparison can never be true doesn't mean that it's illegal. Nonetheless, no, a value type can ever be null.

AK_
  • 7,981
  • 7
  • 46
  • 78
Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
  • 1
    But a value type could be **equal** to `null`. Consider `int?`, which is syntactic sugar for `Nullable`, which is a value type. A variable of type `int?` could certainly be equal to `null`. – Greg Dec 29 '09 at 00:36
  • 1
    @Greg: Yes, it can be equal to null, assuming that the "equal" you're referring to is the result of the `==` operator. It's important to note that the instance is not *actually* null, though. – Adam Robinson Dec 29 '09 at 02:10
3

No, Int32 x won't ever become null.

If you are comparing an int to null then the comparison operator that takes two int?s is applicable.

"Why a comparison of a value type with null is a warning?" article will help you.

Li0liQ
  • 11,158
  • 35
  • 52
1

A value type cannot be null, although it could be equal to null (consider Nullable<>). In your case the int variable and null are implicitly cast to Nullable<Int32> and compared.

Greg
  • 23,155
  • 11
  • 57
  • 79
0

I suspect that your particular test is just being optimized out by the compiler when it generates the IL since the test will never be false.

Side Note: It is possible to have a nullable Int32 use Int32? x instead.

GrayWizardx
  • 19,561
  • 2
  • 30
  • 43
0

I guess this is because "==" is a syntax sugar which actually represents call to System.Object.Equals method that accepts System.Object parameter. Null by ECMA specification is a special type which is of course derived from System.Object.

That's why there's only a warning.

Vitaly
  • 2,567
  • 5
  • 29
  • 34
  • This is not correct, for two reasons. First, == does not have the same semantics as Object.Equals when one of its arguments is a reference type. Second, null is not a type. See section 7.9.6 of the specification if you want to understand how the reference equality operator works. – Eric Lippert Dec 29 '09 at 00:38
  • "The null literal (§9.4.4.6) evaluates to the null value, which is used to denote a reference not pointing at any object or array, or the absence of a value. The null type has a single value, which is the null value. Hence an expression whose type is the null type can evaluate only to the null value. There is no way to explicitly write the null type and, therefore, no way to use it in a declared type." -- this is quote from ECMA. What are you talking about? Also which version of ECMA do you use? I don't see 7.9.6 in mine. – Vitaly Dec 31 '09 at 05:01
0

[EDITED: made warnings into errors, and made operators explicit about nullable rather than the string hack.]

As per @supercat's clever suggestion in a comment above, the following operator overloads allow you to generate an error about comparisons of your custom value type to null.

By implementing operators that compare to nullable versions of your type, the use of null in a comparison matches the nullable version of the operator , which lets you generate the error via the Obsolete attribute.

Until Microsoft gives us back our compiler warning I'm going with this workaround, thanks @supercat!

public struct Foo
{
    private readonly int x;
    public Foo(int x)
    {
        this.x = x;
    }

    public override string ToString()
    {
        return string.Format("Foo {{x={0}}}", x);
    }

    public override int GetHashCode()
    {
        return x.GetHashCode();
    }

    public override bool Equals(Object obj)
    {
        return x.Equals(obj);
    }

    public static bool operator ==(Foo a, Foo b)
    {
        return a.x == b.x;
    }

    public static bool operator !=(Foo a, Foo b)
    {
        return a.x != b.x;
    }

    [Obsolete("The result of the expression is always 'false' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator ==(Foo a, Foo? b)
    {
        return false;
    }
    [Obsolete("The result of the expression is always 'true' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator !=(Foo a, Foo? b)
    {
        return true;
    }
    [Obsolete("The result of the expression is always 'false' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator ==(Foo? a, Foo b)
    {
        return false;
    }
    [Obsolete("The result of the expression is always 'true' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator !=(Foo? a, Foo b)
    {
        return true;
    }
}
yoyo
  • 8,310
  • 4
  • 56
  • 50
  • Unless I'm missing something, your approach will cause the compiler to squawk at `Foo a; Foo? b; ... if (a == b)...`, even though such a comparison should be perfectly legitimate. The reason I suggested the "string hack" is that it would allow the above comparison but squawk at `if (a == null)`. Instead of using `string`, one could substitute any reference type other than `Object` or `ValueType`; if desired, one could define a dummy class with a private constructor that could never be called and entitle it `ReferenceThatCanOnlyBeNull`. – supercat Jul 24 '15 at 19:14
  • You are absolutely correct. I should have clarified that my suggestion breaks the use of nullables ... which in the codebase I am working are considered sinful anyway (unwanted boxing, etc.). ;) – yoyo Jul 24 '15 at 22:21
0

I think the best answer as to why the compiler accepts this is for generic classes. Consider the following class...

public class NullTester<T>
{
    public bool IsNull(T value)
    {
        return (value == null);
    }
}

If the compiler didn't accept comparisons against null for value types, then it would essentially break this class, having an implicit constraint attached to its type parameter (i.e. it would only work with non-value-based types).

Lee.J.Baxter
  • 495
  • 4
  • 12
0

The compiler will allow you to compare any struct implementing the == to null. It even allows you to compare an int to null (you would get a warning though).

But if you disassemble the code you will see that the comparison is being solved when the code is compiled. So, for instance, this code (where Foo is a struct implementing ==):

public static void Main()
{
    Console.WriteLine(new Foo() == new Foo());
    Console.WriteLine(new Foo() == null);
    Console.WriteLine(5 == null);
    Console.WriteLine(new Foo() != null);
}

Generates this IL:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       45 (0x2d)
  .maxstack  2
  .locals init ([0] valuetype test3.Program/Foo V_0)
  IL_0000:  nop
  IL_0001:  ldloca.s   V_0
  IL_0003:  initobj    test3.Program/Foo
  IL_0009:  ldloc.0
  IL_000a:  ldloca.s   V_0
  IL_000c:  initobj    test3.Program/Foo
  IL_0012:  ldloc.0
  IL_0013:  call       bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo,
                                                           valuetype test3.Program/Foo)
  IL_0018:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_001d:  nop
  IL_001e:  ldc.i4.0
  IL_001f:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_0024:  nop
  IL_0025:  ldc.i4.1
  IL_0026:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_002b:  nop
  IL_002c:  ret
} // end of method Program::Main

As you can see:

Console.WriteLine(new Foo() == new Foo());

Is translated to:

IL_0013:  call       bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo,
                                                               valuetype test3.Program/Foo)

Whereas:

Console.WriteLine(new Foo() == null);

Is translated to false:

IL_001e:  ldc.i4.0
hardkoded
  • 18,915
  • 3
  • 52
  • 64
0

I'm writing because checking compiler output yields a somewhat different picture than the accepted answer, which speaks of operator overload. To see this, consider this variation of the code from the question:

int x = 1;
int? y = null;

if (x == y)
{
    System.Console.WriteLine("What the?");
}

As can be seen with https://sharplab.io/ for example, this corresponds to the following lower-level C#:

int num = 1;
Nullable<int> num2 = null;
if ((num == num2.GetValueOrDefault()) & num2.HasValue)
{
    Console.WriteLine("What the?");
}

There is no operator overload here, at least no runtime use of a core library operator that takes two nullables. Rather, the nullable y is turned into a plain integer by GetValueOrDefault. In the special case where y is null, the overall result is false because of HasValue.

(The reason why I did not take the exact code from the question is that the comparison expression would be replaced by false during compilation - that is, the compiler intelligence would obfuscate the issue.)

Carsten Führmann
  • 3,119
  • 4
  • 26
  • 24