35

我一直认为this在实例方法体内不可能为空。以下简单的程序表明这是可能的。这是一些记录在案的行为吗?

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# 的设计方式。

我们从某个人(可能是使用 C# 的 .NET 小组之一(当时还没有命名为 C#))那里得到了一份报告,他编写了在空指针上调用方法的代码,但他们没有得到一个异常,因为该方法没有访问任何字段(即“this”为空,但方法中没有使用它)。然后那个方法调用了另一个方法,它确实使用了这个点并抛出了一个异常,随之而来的是一些令人头疼的问题。在他们弄清楚之后,他们给我们发了一张关于它的便条。我们认为能够在空实例上调用方法有点奇怪。Peter Golde 做了一些测试,看看总是使用 callvirt 对性能的影响是什么,它足够小,我们决定做出改变。

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

4

5 回答 5

23

因为你正在传递nullfirstArgumentDelegate.CreateDelegate

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

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

如果 firstArgument 是空引用并且 method 是实例方法,则结果取决于委托类型类型和方法的签名:

如果类型的签名显式地包含方法的隐藏的第一个参数,则称该委托代表一个开放的实例方法。当委托被调用时,参数列表中的第一个参数被传递给方法的隐藏实例参数。

如果方法和类型的签名匹配(即所有参数类型都兼容),则称委托在空引用上关闭。调用委托就像在空实例上调用实例方法一样,这并不是一件特别有用的事情。

于 2012-05-16T19:30:23.470 回答
12

如果您使用 call IL 指令或委托方法,当然可以调用方法。仅当您尝试访问将给您提供您确实寻求的 NullReferenceException 的成员字段时,您才会关闭此诱杀装置。

尝试

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

BCL 甚至包含明确的 this == null 检查,以帮助调试不一直使用 callvirt 的语言(如 C#)。有关更多信息,请参阅此问题

例如 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);
}
于 2012-05-16T19:30:17.103 回答
5

尝试Delegate.CreateDelegate() 在 msdn的文档。

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

于 2012-05-16T19:31:08.580 回答
5

thisnull是一个引用,所以从类型系统的角度来看它的存在是没有问题的。

你可能会问为什么NullReferenceException没有被抛出。记录了 CLR 引发该异常的完整情况列表。您的案例未列出。是的,它一个callvirt, 但是 to Delegate.Invoke见这里)而不是 to Bar,所以this引用实际上是你的非空委托!

您看到的行为对 CLR 有一个有趣的实现结果。委托具有非常频繁的Target属性(对应于您的引用) ,即当委托是静态的(想象是静态的)时。现在自然有一个私有的属性支持字段,称为. 静态委托是否包含空值? 不,它没有。 它包含对委托本身的引用。为什么不为空?因为如您的示例所示,null 是委托的合法目标,并且 CLR 没有两种类型的指针来以某种方式区分静态委托。thisnullBar_target_targetnull

这段琐碎的事情表明,对于委托,实例方法的空目标不是事后考虑的。您可能仍在问最终的问题:但为什么必须支持它们?

早期的 CLR 有一个雄心勃勃的计划,即成为甚至是宣誓 C++ 开发人员的首选平台,这个目标首先通过托管 C++ 实现,然后通过 C++/CLI 实现。一些过于具有挑战性的语言特性被省略了,但是在支持没有实例的情况下执行实例方法并没有什么真正的挑战,这在 C++ 中是完全正常的。包括代表支持。

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

更好的阅读甚至更好的阅读来展示允许空实例的设计即使在非常自然的 C# 句法上下文中也能显示出它的痕迹。

于 2012-05-16T19:33:20.807 回答
0

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

this == null // readonly - possible
this = new this() // write - not possible
于 2012-11-21T05:34:13.223 回答