为什么委托中“this”指针为空?

时间:2010-08-03 13:14:17

标签: c# .net

我有以下代码(为清晰起见,删除了详细信息):

    private abstract class Base<TResult> {
        private readonly System.Func<TResult> func = null;

        protected Base(System.Func<TResult> func) {
            this.func = func;
        }

        public TResult Execute() {
            return this.func();
        }
    }

    private class Derived : Base<bool> {
        public Derived(bool myValue) : base(delegate() { return this.MyValue(); }) {
            this.myValue = myValue;
        }

        private bool myValue = false;
        private bool MyValue() {
            return this.myValue; // The "this" pointer is null here...
        }
    }

    Derived d = new Derived(true);
    bool result = d.Execute(); // This results in a null reference pointer (commented above)

有什么想法吗?

谢谢,戴夫

4 个答案:

答案 0 :(得分:6)

甚至合法?此时未定义this。 IIRC,这是一个编译器错误 - 已在4.0中修复。

这是在4.0编译器中:

  

错误1关键字'this'在当前上下文中不可用C:\ Users \ Marc \ AppData \ Local \ Temporary Projects \ ConsoleApplication1 \ Program.cs 22 40 ConsoleApplication1

引用7.5.7:

  

仅允许此访问权限在实例构造函数的块中,实例方法或实例访问者。它具有以下含义之一:

(emph mine)

...

  

在上面列出的上下文中的primary-expression中使用this是编译时错误。特别是,无法在静态方法,静态属性访问器或字段声明的变量初始化程序中引用它。

在给出的例子中,它只是无效。

答案 1 :(得分:5)

在构造函数中使用this总是很危险的(除非在您调用兄弟构造函数的特殊情况下)。您的Derived构造函数在调用时捕获this,因为尚未构造实例,所以它为null。

答案 2 :(得分:1)

这是编译器错误,非常奇怪。让我解释一下细节。如果有专家澄清,我会非常高兴。

是的,在ctor中捕获this是不正确的,但情况变得很热,因为在匿名委托中使用了this。通常,如果匿名委托没有闭包(不捕获外部变量),则它由编译器实现为同一类的静态方法。它发生在这里。但是让我们看一下由该静态方法生成的IL代码:

.method private hidebysig static bool <.ctor>b__0() cil managed
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
    .maxstack 1
    .locals init (
        [0] bool CS$1$0000)
    L_0000: nop 
    L_0001: ldloc.0 
    L_0002: call instance bool ConsoleApplication15.Derived::MyValue()
    L_0007: stloc.0 
    L_0008: br.s L_000a
    L_000a: ldloc.0 
    L_000b: ret 
}
你看到了吗?仔细查看第L_0002行和第L_0001行。有两个非常奇怪的事情:

  1. 我们尝试针对MyValue
  2. 调用方法bool
  3. 该方法被称为call,但它不是静态的,C#编译器通常使用callvirt调用实例方法!如果使用了callvirt,则此调用将失败,因为callvirt会检查this == null
  4. 现在让我们打破它。让我们介绍闭包和更改代码:

    public Derived(bool myValue) : base(delegate() { return myValue ^ this.MyValue(); }) {
        this.myValue = myValue;
    }
    

    现在一切都很好!没有NRE!生成了匿名类,它的字段捕获了闭包。在这种情况下,生成正确的IL:

    .method public hidebysig instance bool <.ctor>b__0() cil managed
    {
        .maxstack 2
        .locals init (
            [0] bool CS$1$0000)
        L_0000: nop 
        L_0001: ldarg.0 
        L_0002: ldfld bool ConsoleApplication15.Derived/<>c__DisplayClass1::myValue
        L_0007: ldarg.0 
        L_0008: ldfld class ConsoleApplication15.Derived ConsoleApplication15.Derived/<>c__DisplayClass1::<>4__this
        L_000d: call instance bool ConsoleApplication15.Derived::MyValue()
        L_0012: xor 
        L_0013: stloc.0 
        L_0014: br.s L_0016
        L_0016: ldloc.0 
        L_0017: ret 
    }
    

    针对关闭调用此方法。 (但仍然使用call instance,嗯)

答案 3 :(得分:0)

通过查看您的代码,我会说这是一个设计问题......

为什么不将Execute函数抽象化,让派生类提供他们想要的任何实现?

例如:

private abstract class Base<TResult> {
    public abstract TResult Execute();
}

private class Derived : Base<bool> {
    //...

    public override bool Execute(){
         return this.myValue;
    }

    //....
}

Derived d = new Derived(true);
bool result = d.Execute(); //This should work now