Bri*_*eon 71 .net c# clr reflector
偶尔我喜欢花一些时间查看.NET代码,看看事情是如何在幕后实现的.我在String.Equals通过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);
}
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)
什么是检查的理由this反对null?我必须假设有目的,否则这可能会被抓住并被删除.
Jon*_*eet 85
我假设你在看.NET 3.5实现?我相信.NET 4的实现略有不同.
但是,我有一种潜在的怀疑,这是因为它甚至可以在空引用上非虚拟地调用虚拟实例方法.可能在IL中,即.我会看看是否可以产生一些可以调用的IL null.Equals(null).
编辑:好的,这是一些有趣的代码:
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 17 (0x11)
.maxstack 2
.locals init (string V_0)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldnull
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
IL_000a: call void [mscorlib]System.Console::WriteLine(bool)
IL_000f: nop
IL_0010: ret
} // end of method Test::Main
Run Code Online (Sandbox Code Playgroud)
我通过编译以下C#代码得到了这个:
using System;
class Test
{
static void Main()
{
string x = null;
Console.WriteLine(x.Equals(null));
}
}
Run Code Online (Sandbox Code Playgroud)
...然后拆解ildasm和编辑.注意这一行:
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
Run Code Online (Sandbox Code Playgroud)
最初,那callvirt不是call.
那么,当我们重新组装时会发生什么?那么,使用.NET 4.0,我们得到了这个:
Unhandled Exception: System.NullReferenceException: Object
reference not set to an instance of an object.
at Test.Main()
Run Code Online (Sandbox Code Playgroud)
嗯.用.NET 2.0怎么样?
Unhandled Exception: System.NullReferenceException: Object reference
not set to an instance of an object.
at System.String.EqualsHelper(String strA, String strB)
at Test.Main()
Run Code Online (Sandbox Code Playgroud)
现在这更有意思......我们已经明确地设法进入EqualsHelper,这是我们通常不会想到的.
足够的字符串...让我们自己尝试实现引用相等,看看我们是否可以null.Equals(null)返回true:
using System;
class Test
{
static void Main()
{
Test x = null;
Console.WriteLine(x.Equals(null));
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override bool Equals(object other)
{
return other == this;
}
}
Run Code Online (Sandbox Code Playgroud)
相同的步骤,之前-拆卸,更改callvirt到call,重新组装,然后看着它打印true...
请注意,虽然另一个答案引用了这个C++问题,但我们在这里更加狡猾...因为我们非虚拟地调用虚拟方法.通常,即使C++/CLI编译器也会callvirt用于虚拟方法.换句话说,我认为在这种特殊情况下,thisnull 的唯一方法是手工编写IL.
编辑:我刚刚发现了一些......我本来就不是调用正确的方法无论是我们的小示例程序.这是第一种情况下的电话:
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
Run Code Online (Sandbox Code Playgroud)
这是第二个电话:
IL_0005: call instance bool [mscorlib]System.Object::Equals(object)
Run Code Online (Sandbox Code Playgroud)
在第一种情况下,我打算打电话System.String::Equals(object),而在第二种情况下,我打算打电话Test::Equals(object).从这里我们可以看到三件事:
object.Equals(object) 很高兴比较一个null"this"引用如果向C#覆盖添加一些控制台输出,您可以看到区别 - 除非您更改IL以显式调用它,否则不会调用它,如下所示:
IL_0005: call instance bool Test::Equals(object)
Run Code Online (Sandbox Code Playgroud)
那么,我们就是.在空引用上有趣和滥用实例方法.
如果你做了这一步,你可能也想看看我的博客文章约值类型如何可以声明参数构造函数 ...在IL.
Jar*_*Par 17
原因是它确实可能this存在null.有2个IL操作码可用于调用函数:call和callvirt.callvirt函数使CLR在调用方法时执行空检查.调用指令不并因此允许的方法与输入this是null.
听起来很吓人?确实有点儿.然而,大多数编译器确保不会发生这种情况.只有在null不可能的情况下才会输出.call指令(我很确定C#总是使用callvirt).
但是对于所有语言都不是这样,并且由于我不确切知道BCL团队System.String在这种情况下选择进一步强化该类.
弹出的另一种情况是反向pinvoke调用.
简短的回答是像C#这样的语言迫使你在调用方法之前创建这个类的实例,但框架本身却没有.在CIL中有两种不同的方式来调用函数:call和callvirt....一般来说,C#将始终发出callvirt,这需要this不为空.但其他语言(C++/CLI可以想到)可能会发出call,但没有那种期望.
(¹okay,如果算上愈伤组织,newobj等,它更像是五个,但让我们保持简单)