将结构比较为null

时间:2010-01-07 17:57:13

标签: c# null struct

  

可能重复:
  C# okay with comparing value types to null

我正在多线程环境中处理Windows应用程序,有时会得到异常“在创建窗口句柄之前,无法在控件上调用Invoke或BeginInvoke。”所以我想我只想添加这行代码:

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

但这并没有解决问题。所以我进一步挖掘,并意识到IntPtr(Form.Handle所属的类型)是一个不能为空的结构。这是有效的解决方案:

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

那么它打击了我,为什么它甚至在我检查它为null时编译?所以我决定自己尝试一下:

    public struct Foo { }

然后:

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

并且确实没有编译说“Error 1 Operator'=='不能应用于'ConsoleApplication1.Foo'和'''类型的操作数。好的,那么我开始查看IntPtr的元数据,并开始将所有内容添加到IntPtr结构中的Foo结构(ISerializable,ComVisible),但没有任何帮助。最后,当我添加运算符重载==和!=时,它起作用了:

[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; }
}

这最终编译:

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

我的问题是为什么?如果你覆盖==和!=,为什么你可以比较为null? ==和!=的参数仍然是Foo类型,它们不可为空,所以为什么这会突然允许?

6 个答案:

答案 0 :(得分:17)

看起来问题是当MS引入可空类型时,它们使得每个结构都可以隐式转换为可以为空的类型(foo?),所以代码

if( f == null)

相当于

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

由于MSDN声明“任何用于值类型的用户定义的运算符也可以由可空类型使用”,当您覆盖operator==时,您允许编译隐式转换,因为您现在拥有一个用户-defined == - 免费为您提供可空的重载。

旁白:

在您的示例中似乎有一些编译器优化 编译器发出的唯一一个甚至提示有测试的东西就是这个IL:

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

请注意,如果您将main更改为

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

它不再编译。但是如果你装结构:

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

如果编译,发出IL,并按预期运行(结构永远不为null);

答案 1 :(得分:5)

这与序列化或COM无关 - 所以值得从等式中删除它。例如,这是一个简短但完整的程序,用于演示问题:

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

我认为这是编译因为从null文字到Nullable<Foo>的隐式转换,而你可以合法地执行此操作:

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

有趣的是,只有当==超载时才会发生这种情况 - 马克格拉维尔之前已经发现了这一点。我不知道它是实际上是编译器错误,还是只是解决转换,重载等方式非常微妙的问题。

一些案例中(例如intdecimal),编译器会警告您隐式转换 - 但在其他情况下(例如Guid)它不会“T

答案 2 :(得分:3)

我能想到的是,你的==运算符重载使编译器可以选择:

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

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

并且两者都可以从中选择它能够将左侧转换为对象并使用前者。当然,如果您尝试根据代码运行某些内容,则不会进入运算符重载。由于操作员之间没有选择,编译器显然正在进行一些进一步的检查。

答案 3 :(得分:1)

我相信当你重载一个操作符时,你明确地认为你将处理特定操作符所需的所有逻辑。因此,如果它被击中,你有责任在运算符重载方法中处理null。在这种情况下,我相信你可能已经注意到,如果你比较null,那么重载的方法永远不会被击中。

真正有趣的是,在Henks answer here之后,我在反射器中检查了以下代码。

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

Console.ReadKey();

这就是反射器的表现。

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

编译器清理它,因此重载的操作符方法甚至不会被调用。

答案 4 :(得分:1)

struct没有定义重载“==”或“!=”,这就是你得到原始错误的原因。一旦将重载添加到结构中,比较就是合法的(来自编译器的预期)。作为运算符重载的创建者,你有责任处理这个逻辑(显然微软在这种情况下错过了这个)。

根据你的结构的实现(以及它代表什么),与null的比较可能是完全有效的,这就是为什么这是可能的。

答案 5 :(得分:-1)