这 == 在 .NET 实例方法中为 null - 为什么可以

本文关键字:为什么 null NET 实例方法 | 更新日期: 2023-09-27 18:34:28

>我一直认为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# 的设计方式。

我们从某人那里得到了一份报告(可能是 .NET 组之一 使用 C#(当时认为它还没有命名为 C#((谁有 编写在 null 指针上调用方法的代码,但他们没有 获取异常,因为该方法未访问任何字段(即 "this"为空,但方法中没有任何内容使用它(。然后那个方法 调用了另一个确实使用了这一点并抛出的方法 例外,随之而来的是一点挠头。在他们想通之后 出去,他们给我们发了一张关于它的便条。 我们认为能够在空实例上调用方法是一个 有点奇怪。彼得·戈尔德做了一些测试,看看性能的影响 总是使用callvirt,它足够小,我们决定 进行更改。

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

这 == 在 .NET 实例方法中为 null - 为什么可以

因为你正在传递null Delegate.CreateDelegatefirstArgument

因此,您正在对 null 对象调用实例方法。

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

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

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

如果方法和类型的签名匹配(即所有参数 类型是兼容的(,则称委托在 空引用。调用委托就像调用实例 空实例上的方法,这不是一个特别有用的东西 做。

当然,

如果您使用的是调用 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);
}

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

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

this是一个

引用,所以从类型系统的角度来看,null它没有问题。

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

您看到的行为对 CLR 具有有趣的实现结果。 委托有一个经常nullTarget属性(对应于您的this引用(,即当委托是静态的(想象一下Bar静态的(。 现在,自然而然地,该物业有一个私人支持领域,称为 _target . _target是否包含静态委托的 null? 不,它没有。 它包含对委托本身的引用。 为什么不为空? 因为 null 是委托的合法目标,如您的示例所示,并且 CLR 没有两种null指针来区分静态委托。

这点琐事表明,对于委托,实例方法的空目标不是事后的想法。你可能仍然在问终极问题:但为什么他们必须得到支持?

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

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

更好的阅读

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

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

this == null // readonly - possible
this = new this() // write - not possible