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?
All I can think is that your overloading of the == operator gives the compiler a choice between:
and
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.
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.
This is what reflector showed.
Compiler cleans it up and hence the overloaded operator methods never even get called.
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.
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
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:
I believe this compiles because there's an implicit conversion from the null literal to
Nullable<Foo>
and you can do this legally: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.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 codeis equivalent to
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:
Note that if you change main to
It no longer compiles. But if you box the struct:
if compiles, emits IL, and runs as expected (the struct is never null);