我正在多线程环境中处理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类型,它们不可为空,所以为什么这会突然允许?
答案 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);
有趣的是,只有当==超载时才会发生这种情况 - 马克格拉维尔之前已经发现了这一点。我不知道它是实际上是编译器错误,还是只是解决转换,重载等方式非常微妙的问题。
在一些案例中(例如int
,decimal
),编译器会警告您隐式转换 - 但在其他情况下(例如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)