Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing structs to null [duplicate]

Tags:

c#

null

struct

Possible Duplicate:
C# okay with comparing value types to null

I was working on a windows app in a multithreaded environment and would sometimes get the exception "Invoke or BeginInvoke cannot be called on a control until the window handle has been created." So I figured that I'd just add this line of code:

if(this.Handle != null)
{
   //BeginInvokeCode
}

But that didn't solve the problem. So I dug a little further, and realized that IntPtr (the type that Form.Handle is) is a struct which can't be nullable. This was the fix that worked:

if(this.Handle != IntPtr.Zero)
{
   //BeginInvokeCode
}

So then it hit me, why did it even compile when I was checking it for null? So I decided to try it myself:

    public struct Foo { }

and then:

    static void Main(string[] args)
    {
        Foo f = new Foo();
        if (f == null) { }
    }

and sure enough it didn't compile saying that "Error 1 Operator '==' cannot be applied to operands of type 'ConsoleApplication1.Foo' and ''". Ok, so then I started looking at the metadata for IntPtr and started adding everything to my Foo struct that was there in the IntPtr struct (ISerializable, ComVisible) but nothing helped. Finally, when I added the operator overloading of == and !=, it worked:

[Serializable]
[ComVisible(true)]
public struct Foo : ISerializable
{
    #region ISerializable Members

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        throw new NotImplementedException();
    }

    #endregion

    public override bool Equals(object obj)
    {
        return base.Equals(obj);
    }

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

    public static bool operator ==(Foo f1, Foo f2) { return false; }
    public static bool operator !=(Foo f1, Foo f2) { return false; }
}

This finally compiled:

    static void Main(string[] args)
    {
        Foo f = new Foo();
        if (f == null) { }
    }

My question is why? Why if you override == and != are you allowed to compare to null? The parameters to == and != are still of type Foo which aren't nullable, so why's this allowed all of a sudden?

like image 221
BFree Avatar asked Jan 07 '10 17:01

BFree


3 Answers

It looks like the issue is that when MS introduced nullable types, they made it so that every struct is implicitly convertable to its nullable type (foo?), so the code

if( f == null)

is equivalent to

if ( (Nullable<foo>)f == (Nullable<foo>)null) 

Since MSDN states that "any user-defined operators that exist for value types may also be used by nullable types", when you override operator==, you allow that implicit cast to compile, as you now have a user-defined == -- giving you the nullable overload for free.

An aside:

Seems like in your example, there is some compiler optimization The only thing that is emitted by the compiler that even hints there was a test is this IL:

ldc.i4.0
ldc.i4.0
ceq
stloc.1   //where there is an unused boolean local

Note that if you change main to

Foo f = new Foo();
object b = null;
if (f == b) { Console.WriteLine("?"); }

It no longer compiles. But if you box the struct:

Foo f = new Foo();
object b = null;
if ((object)f == b) { Console.WriteLine("?"); }

if compiles, emits IL, and runs as expected (the struct is never null);

like image 96
Philip Rieck Avatar answered Sep 30 '22 08:09

Philip Rieck


This has nothing to do with serialization or COM - so it's worth removing that from the equation. For instance, here's a short but complete program which demonstrates the problem:

using System;

public struct Foo
{
    // These change the calling code's correctness
    public static bool operator ==(Foo f1, Foo f2) { return false; }
    public static bool operator !=(Foo f1, Foo f2) { return false; }

    // These aren't relevant, but the compiler will issue an
    // unrelated warning if they're missing
    public override bool Equals(object x) { return false; }
    public override int GetHashCode() { return 0; }
}

public class Test
{
    static void Main()
    {
        Foo f = new Foo();
        Console.WriteLine(f == null);
    }
}

I believe this compiles because there's an implicit conversion from the null literal to Nullable<Foo> and you can do this legally:

Foo f = new Foo();
Foo? g = null;
Console.WriteLine(f == g);

It's interesting that this only happens when == is overloaded - Marc Gravell has spotted this before. I don't know whether it's actually a compiler bug, or just something very subtle in the way that conversions, overloads etc are resolved.

In some cases (e.g. int, decimal) the compiler will warn you about the implicit conversion - but in others (e.g. Guid) it doesn't.

like image 29
Jon Skeet Avatar answered Sep 30 '22 09:09

Jon Skeet


All I can think is that your overloading of the == operator gives the compiler a choice between:

public static bool operator ==(object o1, object o2)

and

public static bool operator ==(Foo f1, Foo f2)

and that with both to choose from it is able to cast the left to object and use the former. Certainly if you try to run something based on your code, it doesn't step into your operator overload. With no choice between operators, the compiler is clearly carrying out some further checking.

like image 45
David M Avatar answered Sep 30 '22 08:09

David M