将结构比较为null

BFr*_*ree 37 c# null struct

可能重复:
C#可以将值类型与null进行比较

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

if(this.Handle != null)
{
   //BeginInvokeCode
}
Run Code Online (Sandbox Code Playgroud)

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

if(this.Handle != IntPtr.Zero)
{
   //BeginInvokeCode
}
Run Code Online (Sandbox Code Playgroud)

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

    public struct Foo { }
Run Code Online (Sandbox Code Playgroud)

然后:

    static void Main(string[] args)
    {
        Foo f = new Foo();
        if (f == null) { }
    }
Run Code Online (Sandbox Code Playgroud)

并且肯定它没有编译说"错误1运算符'=='不能应用于'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; }
}
Run Code Online (Sandbox Code Playgroud)

最后编译:

    static void Main(string[] args)
    {
        Foo f = new Foo();
        if (f == null) { }
    }
Run Code Online (Sandbox Code Playgroud)

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

Phi*_*eck 18

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

if( f == null)
Run Code Online (Sandbox Code Playgroud)

相当于

if ( (Nullable<foo>)f == (Nullable<foo>)null) 
Run Code Online (Sandbox Code Playgroud)

由于MSDN声明"任何用于值类型的用户定义的运算符也可以由可空类型使用",因此当您覆盖时operator==,允许隐式转换编译,因为您现在拥有用户定义的== - 给您免费的可空过载.

旁边:

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

ldc.i4.0
ldc.i4.0
ceq
stloc.1   //where there is an unused boolean local
Run Code Online (Sandbox Code Playgroud)

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

Foo f = new Foo();
object b = null;
if (f == b) { Console.WriteLine("?"); }
Run Code Online (Sandbox Code Playgroud)

它不再编译.但是如果你打结了结构:

Foo f = new Foo();
object b = null;
if ((object)f == b) { Console.WriteLine("?"); }
Run Code Online (Sandbox Code Playgroud)

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


Jon*_*eet 6

这与序列化或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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我相信这是编译因为从null文字到隐式转换Nullable<Foo>,你可以合法地做到这一点:

Foo f = new Foo();
Foo? g = null;
Console.WriteLine(f == g);
Run Code Online (Sandbox Code Playgroud)

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

某些情况下(例如int,decimal)编译器会警告你的隐式转换-但在其他人(例如Guid)它没有.