为什么要检查这个!= null?
有时,我喜欢花一些时间查看 .NET 代码,只是为了了解幕后是如何实现的。我在通过 Reflector 查看 String.Equals 方法时偶然发现了这个宝石。
C#
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(object obj)
{
string strB = obj as string;
if ((strB == null) && (this != null))
{
return false;
}
return EqualsHelper(this, strB);
}
IL
.method public hidebysig virtual instance bool Equals(object obj) cil managed
{
.custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency, valuetype System.Runtime.ConstrainedExecution.Cer) = { int32(3) int32(1) }
.maxstack 2
.locals init (
[0] string str)
L_0000: ldarg.1
L_0001: isinst string
L_0006: stloc.0
L_0007: ldloc.0
L_0008: brtrue.s L_000f
L_000a: ldarg.0
L_000b: brfalse.s L_000f
L_000d: ldc.i4.0
L_000e: ret
L_000f: ldarg.0
L_0010: ldloc.0
L_0011: call bool System.String::EqualsHelper(string, string)
L_0016: ret
}
针对 null
检查 this
的原因是什么?我必须假设这是有目的的,否则现在可能已经被捕获并删除了。
Occasionally I like to spend some time looking at the .NET code just to see how things are implemented behind the scenes. I stumbled upon this gem while looking at the String.Equals
method via Reflector.
C#
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(object obj)
{
string strB = obj as string;
if ((strB == null) && (this != null))
{
return false;
}
return EqualsHelper(this, strB);
}
IL
.method public hidebysig virtual instance bool Equals(object obj) cil managed
{
.custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency, valuetype System.Runtime.ConstrainedExecution.Cer) = { int32(3) int32(1) }
.maxstack 2
.locals init (
[0] string str)
L_0000: ldarg.1
L_0001: isinst string
L_0006: stloc.0
L_0007: ldloc.0
L_0008: brtrue.s L_000f
L_000a: ldarg.0
L_000b: brfalse.s L_000f
L_000d: ldc.i4.0
L_000e: ret
L_000f: ldarg.0
L_0010: ldloc.0
L_0011: call bool System.String::EqualsHelper(string, string)
L_0016: ret
}
What is the reasoning for checking this
against null
? I have to assume there is purpose otherwise this probably would have been caught and removed by now.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我假设您正在查看 .NET 3.5 实现?我相信 .NET 4 的实现略有不同。
但是,我偷偷怀疑这是因为甚至可以在空引用上非虚拟地调用虚拟实例方法。也就是说,在伊利诺伊州是可能的。我将看看是否可以生成一些调用
null.Equals(null)
的 IL。编辑:好的,这里有一些有趣的代码:
我通过编译以下 C# 代码得到了这个:
...然后使用 ildasm 反汇编并编辑。请注意这一行:
最初,这是
callvirt
而不是call
。那么,当我们重新组装它时会发生什么呢?好吧,通过 .NET 4.0,我们得到了这个:
嗯。 .NET 2.0 怎么样?
现在更有趣了......我们显然已经成功进入了
EqualsHelper
,这是我们通常不会想到的。足够的字符串...让我们尝试自己实现引用相等,看看我们是否可以让
null.Equals(null)
返回 true:与之前的过程相同 - 反汇编,更改
callvirt< /code> 来
调用
,重新组装,并观察它打印true
...请注意,虽然另一个答案引用了这个 C++ 问题,我们在这里更加狡猾......因为我们正在调用一个虚拟 非虚拟方法。通常,即使是 C++/CLI 编译器也会使用 callvirt 作为虚拟方法。换句话说,我认为在这种特殊情况下,使
this
为 null 的唯一方法是手动编写 IL。编辑:我刚刚注意到一些事情......我实际上并没有在我们的小示例程序中调用正确的方法。这是第一种情况下的调用:
这是第二种情况下的调用:
在第一种情况下,我的意思是调用
System.String::Equals(object)
,然后第二个,我打算调用Test::Equals(object)
。由此我们可以看出三件事:object.Equals(object)
很乐意比较空的“this”引用如果您向 C# 覆盖添加一些控制台输出,您可以看到差异 - 它除非您更改 IL 以显式调用它,否则不会被调用,如下所示:
所以,我们到了。空引用上实例方法的乐趣和滥用。
如果您已经做到了这一点,您可能还想查看我的关于 值类型如何在 IL 中声明无参数构造函数...。
I assume you were looking at the .NET 3.5 implementation? I believe the .NET 4 implementation is slightly different.
However, I have a sneaking suspicion that this is because it's possible to call even virtual instance methods non-virtually on a null reference. Possible in IL, that is. I'll see if I can produce some IL which would call
null.Equals(null)
.EDIT: Okay, here's some interesting code:
I got this by compiling the following C# code:
... and then disassembling with
ildasm
and editing. Note this line:Originally, that was
callvirt
instead ofcall
.So, what happens when we reassemble it? Well, with .NET 4.0 we get this:
Hmm. What about with .NET 2.0?
Now that's more interesting... we've clearly managed to get into
EqualsHelper
, which we wouldn't have normally expected.Enough of string... let's try to implement reference equality ourselves, and see whether we can get
null.Equals(null)
to return true:Same procedure as before - disassemble, change
callvirt
tocall
, reassemble, and watch it printtrue
...Note that although another answers references this C++ question, we're being even more devious here... because we're calling a virtual method non-virtually. Normally even the C++/CLI compiler will use
callvirt
for a virtual method. In other words, I think in this particular case, the only way forthis
to be null is to write the IL by hand.EDIT: I've just noticed something... I wasn't actually calling the right method in either of our little sample programs. Here's the call in the first case:
here's the call in the second:
In the first case, I meant to call
System.String::Equals(object)
, and in the second, I meant to callTest::Equals(object)
. From this we can see three things:object.Equals(object)
is happy to compare a null "this" referenceIf you add a bit of console output to the C# override, you can see the difference - it won't be called unless you change the IL to call it explicitly, like this:
So, there we are. Fun and abuse of instance methods on null references.
If you've made it this far, you might also like to look at my blog post about how value types can declare parameterless constructors... in IL.
原因是
this
确实有可能为null
。有 2 个 IL 操作码可用于调用函数:call 和 callvirt。 callvirt 函数会导致 CLR 在调用该方法时执行 null 检查。调用指令没有,因此允许输入this
为null
的方法。听起来很可怕吗?确实是有一点。然而大多数编译器都会确保这种情况永远不会发生。仅当
null
不可能时才会输出 .call 指令(我非常确定 C# 始终使用 callvirt)。但并非所有语言都如此,并且由于我不太清楚 BCL 团队选择在此实例中进一步强化
System.String
类的原因,情况并非如此。另一种可能弹出此消息的情况是反向 pinvoke 调用。
The reason why is that it is indeed possible for
this
to benull
. There are 2 IL op codes which can be used to invoke a function: call and callvirt. The callvirt function causes the CLR to perform a null check when invoking the method. The call instruction does not and hence allows for a method to be entered withthis
beingnull
.Sound scary? Indeed it is a bit. However most compilers ensure this doesn't ever happen. The .call instruction is only ever outputted when
null
is not a possibility (I'm pretty sure that C# always uses callvirt).This isn't true for all languages though and for reasons I don't exactly know the BCL team chose to further harden the
System.String
class in this instance.Another case where this can popup is in reverse pinvoke calls.
简而言之,像 C# 这样的语言会强制您在调用方法之前创建此类的实例,但框架本身不会。 CIL 中有两种不同的方式来调用函数:
call
和callvirt
...一般来说,C# 总是会发出callvirt
,这要求this
不为空。但其他语言(例如 C++/CLI)可能会发出call
,但它没有这种期望。(好吧,如果算上愈伤组织、newobj 等,它更像是五个,但让我们保持简单)
The short answer is that languages like C# force you to create an instance of this class before calling the method, but the Framework itself does not. There are two¹ different ways in CIL to call a function:
call
andcallvirt
.... Generally speaking, C# will always emitcallvirt
, which requiresthis
to not be null. But other languages (C++/CLI comes to mind) could emitcall
, which doesn't have that expectation.(¹okay, it's more like five if you count calli, newobj etc, but let's keep it simple)
源代码有以下评论:
The source code has this comment:
让我们看看...
this
是您要比较的第一个字符串。obj
是第二个对象。所以看起来这是一种优化。它首先将 obj 转换为字符串类型。如果失败,则strB
为 null。如果strB
为空而this
不为空,那么它们肯定不相等,并且可以跳过EqualsHelper
函数。这将节省函数调用。除此之外,也许更好地理解 EqualsHelper 函数可能会阐明为什么需要这种优化。
编辑:
啊,所以 EqualsHelper 函数接受
(string, string)
作为参数。如果 strB 为 null,则本质上意味着它要么是一个 null 对象,要么无法成功转换为字符串。 如果strB
为 null 的原因是该对象是无法转换为字符串的不同类型,那么您不会希望使用本质上两个 null 值(即会返回 true)。 在这种情况下,Equals 函数应该返回 false。因此,这个 if 语句不仅仅是一种优化,它实际上还确保了正确的功能。Let's see...
this
is the first string you're comparing.obj
is the second object. So it looks like it's an optimization of sorts. It's first castingobj
to a string type. And if that fails, thenstrB
is null. And ifstrB
is null whilethis
isn't, then they're definitely not equal and theEqualsHelper
function can be skipped.That will save a function call. Beyond that, perhaps a better understanding of the
EqualsHelper
function might shed some light on why this optimization is needed.EDIT:
Ah, so the EqualsHelper function is accepting a
(string, string)
as parameters. IfstrB
is null, then that essentially means that it was either a null object to begin with, or it couldn't successfully be cast into a string. If the reason forstrB
being null is that the object was a different type that couldn't be converted to a string then you wouldn't want to call EqualsHelper with essentially two null values (that'll return true). The Equals function should return false in this case. So this if statement is more than an optimization, it actually ensures proper functionality as well.如果参数 (obj) 未转换为字符串,则 strB 将为 null,结果应为 false。示例:
写入
false
。请记住,任何参数类型都会调用 string.Equals() 方法,而不仅仅是其他字符串。
If the argument (obj) does not cast to a string then strB will be null and the result should be false. Example:
writes
false
.Remember that string.Equals() method is called for any argument type, not only for other strings.