C# 将结构与 null 进行比较
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2022425/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Comparing structs to null
提问by BFree
Possible Duplicate:
C# okay with comparing value types to null
可能的重复:
C# 可以将值类型与 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:
我正在多线程环境中处理 Windows 应用程序,有时会收到异常“在创建窗口句柄之前无法在控件上调用 Invoke 或 BeginInvoke”。所以我想我只需要添加这行代码:
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:
但这并没有解决问题。所以我进一步挖掘,并意识到 IntPtr(Form.Handle 的类型)是一个不能为空的结构。这是有效的修复:
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:
那么它击中了我,为什么在我检查它的 null 时它甚至编译?所以我决定自己尝试一下:
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:
果然它没有编译说“错误 1 运算符 '==' 不能应用于类型为 'ConsoleApplication1.Foo' 和 '' 的操作数”。好的,然后我开始查看 IntPtr 的元数据,并开始将所有内容添加到 IntPtr 结构(ISerializable、ComVisible)中的 Foo 结构中,但没有任何帮助。最后,当我添加 == 和 != 的运算符重载时,它起作用了:
[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?
我的问题是为什么?为什么如果覆盖 == 和 != 可以与 null 进行比较?== 和 != 的参数仍然是不可为空的 Foo 类型,那么为什么突然允许呢?
采纳答案by Philip Rieck
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
看起来问题在于,当 MS 引入可空类型时,他们使每个结构都可以隐式转换为其可空类型 ( foo?
),因此代码
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 override operator==
, you allow that implicit cast to compile, as you now have a user-defined == -- giving you the nullable overload for free.
由于 MSDN 指出“存在于值类型的任何用户定义的运算符也可以由可空类型使用”,因此当您覆盖时operator==
,您允许隐式强制转换进行编译,因为您现在有一个用户定义的 == -- 给您免费的可空重载。
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:
似乎在您的示例中,有一些编译器优化编译器发出的唯一提示甚至暗示有测试的是这个 IL:
ldc.i4.0
ldc.i4.0
ceq
stloc.1 //where there is an unused boolean local
Note that if you change main to
请注意,如果您将 main 更改为
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);
if 编译,发出 IL,并按预期运行(结构永远不会为空);
回答by Stan R.
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.
我相信当你重载一个运算符时,你明确地接受了你将使用特定运算符处理所有必要逻辑的概念。因此,您有责任在操作符重载方法中处理 null,如果它被击中。在这种情况下,我相信您可能已经注意到,如果与 null 相比,重载的方法永远不会被命中。
Whats really interesting is that following Henks answer here, i checked out the following code in reflector.
真正有趣的是,在Henks 回答之后,我在反射器中查看了以下代码。
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.
编译器清理它,因此重载的运算符方法甚至永远不会被调用。
回答by lluismontero
I recomend you to take a look to those pages:
我建议你看看这些页面:
http://www.albahari.com/valuevsreftypes.aspx
http://www.albahari.com/valuevsreftypes.aspx
http://msdn.microsoft.com/en-us/library/s1ax56ch.aspx
http://msdn.microsoft.com/en-us/library/s1ax56ch.aspx
回答by Ken Henderson
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).
struct 没有定义重载 "==" 或 "!=" 这就是为什么你得到原始错误的原因。一旦将重载添加到您的结构中,比较就是合法的(从编译器的角度来看)。作为运算符重载的创建者,您有责任处理此逻辑(显然,在这种情况下,Microsoft 忽略了这一点)。
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.
根据您的结构的实现(及其代表的内容),与 null 的比较可能是完全有效的,这就是为什么这是可能的。
回答by David M
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.
并且两者都可以选择它能够将左侧投射到对象并使用前者。当然,如果您尝试根据您的代码运行某些东西,它不会进入您的运算符重载。由于在运算符之间没有选择,编译器显然是在进行一些进一步的检查。
回答by Jon Skeet
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:
这与序列化或 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);
}
}
I believe this compiles because there's an implicit conversion from the null literal to Nullable<Foo>
and you cando this legally:
我相信这可以编译,因为存在从 null 文字到的隐式转换Nullable<Foo>
,您可以合法地执行此操作:
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 actuallya compiler bug, or just something very subtle in the way that conversions, overloads etc are resolved.
有趣的是,这只发生在 == 重载时 - Marc Gravell 之前已经发现了这一点。我不知道这是否真的是编译器错误,或者只是在解决转换、重载等方面非常微妙的问题。
In somecases (e.g. int
, decimal
) the compiler will warn you about the implicit conversion - but in others (e.g. Guid
) it doesn't.
在某些情况下(例如int
, decimal
),编译器会警告您有关隐式转换的信息 - 但在其他情况下(例如Guid
)则不会。