tags:

views:

282

answers:

6

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?

+2  A: 

I believe when you overload an operator you are explicitly subscribing to the notion that you will handle all of the logic necessary with the specific operator. Hence it is your responsibility to handle null in the operator overload method, if it ever gets hit. In this case as I am sure you've probably noticed the overloaded methods never get hit if you compare to null.

Whats really interesting is that following Henks answer here, i checked out the following code in reflector.

Foo f1 = new Foo();
if(f1 == null)
{
  Console.WriteLine("impossible");
}

Console.ReadKey();

This is what reflector showed.

Foo f1 = new Foo();
Console.ReadKey();

Compiler cleans it up and hence the overloaded operator methods never even get called.

Stan R.
... except of course that a struct parameter will never actually be null, hence his question.
Lasse V. Karlsen
right, but I don't think the compiler takes care of it, meaning as far as the compiler is concerned you overloaded the operator. Also the method never even gets called.
Stan R.
A: 

I recomend you to take a look to those pages:

http://www.albahari.com/valuevsreftypes.aspx

http://msdn.microsoft.com/en-us/library/s1ax56ch.aspx

http://msdn.microsoft.com/en-us/library/490f96s2.aspx

lluismontero
I don't think he needs to read any of that, that's not what his question was about.
Stan R.
+1  A: 

struct doesn't define the overloads "==" or "!=" which is why you got the original error. Once the overloads were added to your struct the comparision was legal (from a compiler prospective). As the creator of the operator overload is it your responsibility to handle this logic (obviously Microsoft missed this in this case).

Depending on your implementation of your struct (and what it represents) a comparison to null may be perfectly valid which is why this is possible.

confusedGeek
Nope, we didn't miss this one. (Though the fact that a warning is not reported seems to be a bug.) This behaviour is correct according to the specification.
Eric Lippert
@Eric, Good to know. Thanks
confusedGeek
+3  A: 

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.

David M
Note that without the overload, the first form would still be applicable - but the compiler prohibits it. I think you're certainly along the right lines, but there's deeper magic here.
Jon Skeet
The choice is actually between (obj, obj), (Foo, Foo) and (Foo?, Foo?) -- defining an equality operator on (Foo, Foo) automatically also defines the lifted-to-nullable version.
Eric Lippert
@Eric - perfect explanation, makes total sense. Thanks.
David M
@Jon - I've discovered exactly this case explained in a book - it's on page 126 of *C# in Depth* :)
David M
+3  A: 

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.

Jon Skeet
@Jon, I didn't have to override Equals or GetHashCode to simulate this behavior either.
Stan R.
@Stan: No, I just wanted to get it to compile without warnings. Was about to edit exactly that into the answer :)
Jon Skeet
+5  A: 

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 ovveride 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);

Philip Rieck