C# okay with comparing value types to null

2019-01-01 02:29发布

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

标签: c# null
10条回答
若你有天会懂
2楼-- · 2019-01-01 02:43

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.

查看更多
深知你不懂我心
3楼-- · 2019-01-01 02:47

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
查看更多
荒废的爱情
4楼-- · 2019-01-01 02:50

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.

查看更多
梦醉为红颜
5楼-- · 2019-01-01 02:51

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).

查看更多
只靠听说
6楼-- · 2019-01-01 02:52

[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;
    }
}
查看更多
回忆,回不去的记忆
7楼-- · 2019-01-01 02:54

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.

查看更多
登录 后发表回答