C# 为什么要检查这个!=无效的
有时,我喜欢花一些时间看一下.NET代码,看看在幕后是如何实现的。我在通过Reflector查看C# 为什么要检查这个!=无效的,c#,.net,clr,reflector,C#,.net,Clr,Reflector,有时,我喜欢花一些时间看一下.NET代码,看看在幕后是如何实现的。我在通过Reflector查看字符串.Equals方法时偶然发现了这个宝石 C# IL .method public hidebysig virtual instance bool Equals(object obj) cil managed { .custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.
字符串.Equals
方法时偶然发现了这个宝石
C#
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
}
检查
此
与空
的理由是什么?我必须假设这是有目的的,否则它现在可能已经被捕获并删除了。我假设您正在查看.NET 3.5的实现?我认为.NET4的实现略有不同
然而,我有一种潜在的怀疑,这是因为即使是虚拟实例方法,也可以对空引用进行非虚拟调用。也就是说,在IL中是可能的。我将看看是否可以生成一些调用null.Equals(null)
的IL
编辑:好的,这里有一些有趣的代码:
.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
我是通过编译以下C#代码得到的:
。。。然后使用ildasm
进行反汇编和编辑。请注意这一行:
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
最初,这是callvirt
,而不是call
那么,当我们重新组装它时会发生什么?使用.NET 4.0,我们可以得到:
Unhandled Exception: System.NullReferenceException: Object
reference not set to an instance of an object.
at Test.Main()
那么.NET2.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()
现在这更有趣了。。。显然,我们已经设法进入了equalHelper
,这是我们通常不会想到的
够多的绳子了。。。让我们自己尝试实现引用相等,看看是否可以得到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;
}
}
与之前相同的步骤-拆解,将callvirt
更改为call
,重新组装,并观看打印true
请注意,虽然另一个答案是参考文献,但我们在这里更加狡猾。。。因为我们在非虚拟地调用一个虚拟方法。通常,即使是C++/CLI编译器也会对虚拟方法使用callvirt
。换句话说,我认为在这种特殊情况下,this
为空的唯一方法是手工编写IL
编辑:我刚刚注意到一些事情。。。在我们的两个小示例程序中,我实际上都没有调用正确的方法。以下是第一种情况下的要求:
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
这是第二个电话:
IL_0005: call instance bool [mscorlib]System.Object::Equals(object)
在第一种情况下,我打算调用System.String::Equals(object)
,在第二种情况下,我打算调用Test::Equals(object)
。从中我们可以看到三件事:
- 你需要小心超载
- C#编译器向虚方法的声明器发出调用,而不是虚方法最具体的重写。IIRC,VB的工作方式正好相反
乐于比较空的“this”引用object.Equals(object)
IL_0005: call instance bool Test::Equals(object)
那么,我们到了。null引用上实例方法的有趣和滥用
如果你已经做到了这一点,你可能还想看看我关于。。。在IL.让我们看看
这是您要比较的第一个字符串obj
是第二个对象。所以它看起来像是某种优化。这是第一次将obj
转换为字符串类型。如果失败,则strB
为空。如果strB
为null,而this
为null,则它们肯定不相等,可以跳过equalHelper
函数
这将保存一个函数调用。除此之外,更好地理解EqualsHelper
函数可能有助于了解为什么需要这种优化
编辑:
啊,因此equalHelper函数接受
(string,string)
作为参数。如果strB为null,则本质上意味着它要么是一个null对象,要么无法成功转换为字符串如果strB
为null的原因是对象是无法转换为字符串的不同类型,则您不希望使用两个null值调用EqualHelper(这将返回true)。在这种情况下,Equals函数应返回false。因此,这个if语句不仅仅是一个优化,它实际上也确保了正确的功能。原因是这个
确实可能是空的。有两个IL操作代码可用于调用函数:call和callvirt。调用方法时,callvirt函数使CLR执行空检查。call指令不允许,因此允许在this
为null
的情况下输入方法
听起来吓人吗?确实有点。然而,大多数编译器确保这不会发生。.call指令只有在null
不可能时才会输出(我很确定C#总是使用callvirt)
但并非所有语言都是如此,因为我不清楚BCL团队在本例中选择进一步强化System.String
类的原因
另一种情况是在反向pinvoke调用中会弹出此对话框 简单的回答是,像C#这样的语言强制您在调用该方法之前创建该类的实例,但框架本身没有。在CIL中调用函数有两种不同的方式:call
和callvirt
。。。。一般来说,C#将始终发出callvirt
,这要求this
不为空。但其他语言(我想到的是C++/CLI)可能会发出call
,而这并不是预期的结果
(好的,如果你计算calli、newobj等,它更像是5,但让我们保持简单)如果参数(obj)没有转换为字符串,则strB将为null,结果应为false。例如:
int[] list = {1,2,3};
Console.WriteLine("a string".Equals(list));
写IL_0005: call instance bool Test::Equals(object)
int[] list = {1,2,3};
Console.WriteLine("a string".Equals(list));