C# .NET实例方法内部的this==null-为什么可能?
我一直认为,C# .NET实例方法内部的this==null-为什么可能?,c#,.net,C#,.net,我一直认为,这个在实例方法体中不可能为null。下面的简单程序说明了这是可能的。这是记录在案的行为吗 class Foo { public void Bar() { Debug.Assert(this == null); } } public static void Test() { var action = (Action)Delegate.CreateDelegate(typeof (Action), null, ty
这个
在实例方法体中不可能为null。下面的简单程序说明了这是可能的。这是记录在案的行为吗
class Foo
{
public void Bar()
{
Debug.Assert(this == null);
}
}
public static void Test()
{
var action = (Action)Delegate.CreateDelegate(typeof (Action), null, typeof(Foo).GetMethod("Bar"));
action();
}
更新
我同意答案的说法,这就是这种方法的记录方式。然而,我并不真正理解这种行为。特别是因为它不是C#的设计方式
我们收到了一份来自某个人的报告(可能是某个.NET团体)
使用C#(当时还没有命名为C#))谁
在空指针上调用方法的书面代码,但它们没有
获取异常,因为该方法未访问任何字段(即
“this”为null,但使用它的方法中没有任何内容)。那么这个方法呢
调用另一个使用此点的方法并抛出
例外,接着是一点擦伤。在他们弄明白之后
他们给我们发了一张纸条。
我们认为,能够在空实例上调用方法是一种挑战
有点奇怪。Peter Golde进行了一些测试,以了解性能的影响
总是使用callvirt,而且它足够小,所以我们决定
改变
如果使用call IL指令或委托方法,请确保可以调用方法。只有在尝试访问成员字段时,您才会设置此陷阱,这将为您提供您所寻求的NullReferenceException 试一试 BCL甚至包含显式this==null检查,以帮助调试不始终使用callvirt(如C#)的语言。有关更多信息,请参见此 例如,String类有这样的检查。它们没有什么神秘之处,只是在像C#这样的语言中你看不到它们的必要性
因为您正在将
null
传递到Delegate.CreateDelegate的firstArgument
因此,您正在对空对象调用实例方法
如果firstArgument是空引用,而method是实例方法,
结果取决于委托类型和类型的签名
方法:
如果类型的签名明确包含隐藏的第一个
方法的参数,则该委托被称为表示打开的
实例方法。调用委托时,中的第一个参数
参数列表将传递给的隐藏实例参数
方法
如果方法和类型的签名匹配(即所有参数
类型是兼容的),则表示委托在
空引用。调用委托就像调用实例一样
方法,这对于
做
请尝试Delegate.CreateDelegate()
的文档
您正在“手动”调用所有内容,因此没有为this
指针传递实例,而是传递null。所以这是可能发生的,但是你必须非常努力。这是一个参考,所以从类型系统的角度来看,它是空的没有问题
您可能会问为什么没有抛出NullReferenceException
。CLR引发该异常的情况的完整列表如下所示。您的案例未列出。是的,它是一个callvirt
,但是要委托。调用(),而不是条,因此这个引用实际上是您的非空委托
您看到的行为对于CLR有一个有趣的实现结果。代理具有一个目标
属性(对应于您的此
引用),该属性经常为空
,即当代理为静态时(想象条
为静态)。现在,很自然地,该属性有一个私有支持字段,称为\u target
。\u target
是否包含静态委托的空值?它包含对委托本身的引用。为什么不空?因为null是委托的合法目标,如您的示例所示,CLR没有两种类型的null
指针来区分静态委托
这一点三部曲表明,对于委托,实例方法的空目标不是事后才想到的。你可能仍然在问一个终极问题:但为什么他们必须得到支持
<>早期CLR有一个雄心勃勃的计划,包括,甚至是对发誓的C++开发者的选择平台,一个目标是先用托管C++然后用C++ +CLI来实现的。一些过于具有挑战性的语言特性被忽略了,但是没有支持实例执行的实例方法的挑战,这在C++中是完全正常的。包括代表支持
因此,最终的答案是:因为C#和CLR是两个不同的世界
为了显示允许空实例的设计,即使在非常自然的C#语法上下文中也会显示其痕迹。这是C#类中的只读参考。因此,正如预期的那样,这可以像任何其他引用一样使用(在只读模式下)
请参阅我的答案,以了解CLR是在.NET1.0中故意设计成这样的。您引用的这篇文章是关于一种不同的(非委托)情况的,这将是一种编译器优化—当实例的类型可以静态确定时,用直接调用替换callvirt
。请注意,CLR在callvirt
期间必须抛出NullReferenceException
的原因是需要基于此
引用进行VMT查找;不仅仅是检查引用的语义需求。相关:IMHO,不幸的是,没有标准的方法(可能通过属性)来指定方法应该通过调用而不是调用callvirt
来调用,因为这将允许封装
int x;
public void Bar()
{
x = 1; // NullRefException
Debug.Assert(this == null);
}
// Determines whether two strings match.
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(Object obj)
{
//this is necessary to guard against reverse-pinvokes and
//other callers who do not use the callvirt instruction
if (this == null)
throw new NullReferenceException();
String str = obj as String;
if (str == null)
return false;
if (Object.ReferenceEquals(this, obj))
return true;
return EqualsHelper(this, str);
}
this == null // readonly - possible
this = new this() // write - not possible