Why is the "this" pointer null in a delegate?
I have the following code (details removed for clarity):开发者_如何学JAVA
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)
Any ideas?
Thanks, Dave
Is that even legal? this
is not defined at that point. IIRC, this is a compiler bug - already fixed in 4.0.
Here it is in the 4.0 compiler:
Error 1 Keyword 'this' is not available in the current context C:\Users\Marc\AppData\Local\Temporary Projects\ConsoleApplication1\Program.cs 22 40 ConsoleApplication1
To quote 7.5.7:
A this-access is permitted only in the block of an instance constructor, an instance method, or an instance accessor. It has one of the following meanings:
(emph mine)
...
Use of this in a primary-expression in a context other than the ones listed above is a compile-time error. In particular, it is not possible to refer to this in a static method, a static property accessor, or in a variable-initializer of a field declaration.
In the example given, it it simply invalid.
Using this
in a constructor is always dangerous (except in the special case where you are invoking a sibling constructor). Your Derived
constructor captures this
at the time of its invocation, which is null as the instance hasn't been constructed yet.
It is compiler bug and very weird. Let me explain details. I would be really happy if some experts clarify it.
Yes, it is incorrect to capture this
in ctor, but the situation gets hot because this
was used inside anonymous delegate. Normally, if anonymous delegate doesn't have closure (doesn't capture outer variables) it is implemented by compiler as static method of same class. It happened here. But let's take a look in IL code generated of that static method:
.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
}
did you see that? take closer look at line L_0002
and line L_0001
. There are two extremely strange things:
- We tried to call method
MyValue
againstbool
! - The method was called as
call
but it is not static, C# compiler usually call instance methods withcallvirt
! If thecallvirt
was used this call would fail, becausecallvirt
checks forthis == null
.
Now let's break it. Let's introduce closure and change code to:
public Derived(bool myValue) : base(delegate() { return myValue ^ this.MyValue(); }) {
this.myValue = myValue;
}
And now everything is fine! No NRE! Anonymous class was generated and it's fields capture the closure. In this case correct IL is generated:
.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
}
Method is called against closured this. (but still with call instance
, hmm)
By looking at your code I would say is a design problem...
Why don't you make the Execute
function abstract and let the derived classes provide whatever implementation they want?
For example:
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
精彩评论