2012-05-16 28 views
33

我一直认为this在实例方法体内是不可能的。以下简单的程序表明它是可能的。这是一些记录的行为?这个== null里面的.NET实例方法 - 为什么可能?

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(); 
} 

UPDATE

我同意回答说这是怎么这种方法记录在案。但是,我不太了解这种行为。特别是因为这不是C#的设计方式。

我们已经得到了从别人的报告(使用C#的.NET组 的可能一个(还以为是尚未命名C#当时))谁了 编写代码,上一个空叫的方法指针,但他们没有 得到一个异常,因为该方法没有访问任何字段(即 “this”为空,但在方法中没有使用它)。该方法然后调用另一种方法,它使用了这一点,并抛出了一个异常,并且随后出现了一些头部划伤。在他们认为它是 之后,他们给我们发了一张关于它的记录。 我们认为能够调用一个空实例的方法是有点怪异的 。彼得戈尔德做了一些测试,看看哪些性能冲击 总是使用callvirt,并且它足够小,因此我们决定让 进行更改。

http://blogs.msdn.com/b/ericgu/archive/2008/07/02/why-does-c-always-use-callvirt.aspx

+0

请参阅我的回答,指出CLR是有意设计的,就像.NET 1.0一样。你引用的文章是关于一个不同的(非委托)情况,这将是一个编译器优化 - 当静态确定实例的类型时,可以通过直接调用替换“callvirt”。请注意,CLR必须在'callvirt'期间抛出'NullReferenceException'的原因是需要基于'this'引用的VMT查找;不仅仅是检查参考的语义愿望。 –

+0

相关:http://stackoverflow.com/q/3143498/158779 –

+0

恕我直言,很遗憾没有标准方法(可能通过属性)指定应该用'call'而不是'callvirt'调用方法,因为这将允许封装值的类型[例如'string']具有可以在默认值存储位置上操作的成员。说'如果(someString.IsNullOrEmpty)'将比'if(String.IsNullOrEmpty(someString))'更清洁恕我直言。 – supercat

回答

23

因为,您将nullDelegate.CreateDelegate

firstArgument所以你调用一个空的对象上的实例方法。

http://msdn.microsoft.com/en-us/library/74x8f551.aspx

如果firstArgument为空引用和方法是一个实例方法, 结果取决于委托类型类型的签名和 方法:

如果类型的签名明确包含隐藏的第一个参数 方法,该代表据说代表一个开放的 实例方法。调用委托时,参数列表 中的第一个参数将传递给隐藏实例参数 方法。

如果方法和类型的签名匹配(也就是说,所有参数 类型都是兼容的),那么该委托被称为在空引用上关闭。调用委托就像在一个空实例上调用一个实例 方法,这对 不是特别有用。

12

当然,你可以,如果你正在使用呼叫IL指令或委托方法调用到的方法。如果您尝试访问会导致您所寻找的NullReferenceException的成员字段,您将只设置此陷阱陷阱。

尝试

int x; 
public void Bar() 
{ 
     x = 1; // NullRefException 
     Debug.Assert(this == null); 
} 

的BCL甚至不包含明确这个== null检查,以帮助调试不使用callvirt(如C#)所有的时间语言。有关更多信息,请参阅此question

例如,String类有这样的检查。对他们来说没有任何神秘之处,除了你不会在C#等语言中看到他们的需要。

// 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); 
} 
5

尝试关于Delegate.CreateDelegate()at msdn的文档。

您正在“手动”调用所有内容,因此不会为this指针传入实例,而是传递null。所以可能会发生,但你必须非常努力地尝试。

+0

请参阅以下示例,了解真正简单的方法。 http://bradwilson.typepad.com/blog/2008/01/c-30-extension.html –

+0

扩展方法可能“看起来”像类方法,但它们确实不是。它们只是一些静态方法,它们在一个类实例上运行,并带有一些内置的易用性。所以我不会这么称呼。 –

+0

我同意你的观点,但是列表不会停留在扩展方法中。 C++/CLI直接调用非虚拟C#方法,为您提供与扩展方法示例完全一样的源代码,这次在C#端导致了一个真正的'this == null'。 –

5

this是一个参考,所以从类型系统的角度看它没有问题,因为它是null

你可能会问为什么NullReferenceException没有被抛出。 CLR引发该异常的情况的完整列表是documented。你的情况没有列出。是的,它 a callvirt,但到Delegate.Invokesee here)而不是Bar,所以this引用实际上是您的非空委托!

您看到的行为对CLR有一个有趣的实施结果。代表有一个Target属性(对应于您的this参考),这是相当频繁的null,即当代表是静态的(想象Bar是静态的)。自然,现在有一个私人的财产支持领域,称为_target_target是否包含静态委托的空值? No it doesn't.它包含对委托本身的引用。为什么不是null?因为null是代理的合法目标,因为您的示例显示CLR没有两种风格的指针来以某种方式区分静态代理。

这一点trivium表明,与委托,实例方法的空目标是没有事后才想到的。你可能仍然在问最终的问题:但为什么他们必须得到支持?

早期的CLR有一个雄心勃勃的计划,即使对于发誓的C++开发人员来说也是成为首选的平台,这个目标首先是通过托管C++和C++/CLI来实现的。有些过于具有挑战性的语言特征被忽略,但是对于支持没有实例的情况下执行的实例方法没有任何真正的挑战,这在C++中是完全正常的。包括代表支持。

因此,最终的答案是:因为C#和CLR是两个不同的世界。

More good readingeven better reading显示允许空实例的设计,即使在非常自然的C#语法上下文中也显示其痕迹。

0

这是C#类中的只读引用。因此,如预期的那样,这可以像任何其他参考(只读模式)一样使用... ...

this == null // readonly - possible 
this = new this() // write - not possible 
相关问题