views:

135

answers:

5

I have an XNA 3.0 project that compiled just fine in VS2008, but that gives compile errors in VS2010 (with XNA 4.0 CTP). The error:

Cannot use fixed local 'depthPtr' inside an anonymous method, lambda expression, or query expression

depthPtr is a fixed float* into an array, that is used inside a Parallel.For lambda expression from System.Threading. As I said, this compiled and ran just fine on VS2008, but it does not on VS2010, even when targeting .NET 3.5.

Has this changed in .NET 4.0, and even so, shouldn't it still compile when I choose .NET 3.5 as the target framework? Searching for the term "Cannot use fixed local" yields exactly one (useless) result, both in Google and Bing.

If this has changed, what is the reason for this? I can imagine capturing a fixed pointer-type in a closure could get a bit weird, is that why? So I'm guessing this is bad practice? And before anyone asks: no, the use of pointers is not absolutely critical here. I would still like to know though :)

EDIT: As requested, a code sample (not from my program, obviously) that reproduces the error:

static unsafe void Main(string[] args)
{
  float[] array = new float[10];

  fixed (float* ptr = array)
  {
    Parallel.For(0, 10, i =>
    {
      ptr[i] = i;
    });
  }
}

The above compiles in VS2008 (well, aside from the reference to Parallel, but any other lambda expression will do), but does not in VS2010.

A: 

One explanation could be, that the value of a variable is taken at the execution of the closure, not the definition. In your example it probably wouldn't do any harm, but in other cases it could be. So to teach good practise, it is forbidden altogether to prevent all kinds of interesting bugs.

Femaref
+2  A: 

fixed pins a pointer for the duration of the block. If you were to store the delegate to invoke later after the block had been exited the garbage collector could move the object between when the lambda is created and when the lambda is invoked. As to why targeting a different framework doesn't help, this is because this is being enforced by the language/compiler, not the runtime (if it were the runtime, it would be reported via an exception or similar at runtime, not by the compiler at compile time).

Logan Capaldo
Ah, so it just sets the runtime version and doesn't use an older compiler? Makes sense. About the pinning though, yes that's a risk, but I could just as easily declare an instance field on the type that also lives longer than the fixed block, making it point to an invalid location. Yet that's still possible.
JulianR
I do not think it would be possible to statically check all the scenarios a pinned pointer could become invalid, ultimately someone can always use GCHandle. This check probably simply represents a "best effort" to avoid the non-obvious problems that can ensue from code like this.
Logan Capaldo
+1  A: 

The doco say you are not allowed to access unsafe code in an anonymous methods, and the same restrictions apply to lambdas, so I think that may be your problem. Do you have the actual compiler error no?

LukeN
+1  A: 

This works. Basically we eliminated the lambda containing an unsafe pointer and replaced it with a delegate to an instance of a class declared inside the fixed block.

    static unsafe void UnsafeTest(string[] args) {
        float[] array = new float[10];

        fixed(float* ptr = array) {
            UnsafeOps ops = new UnsafeOps();
            ops.p = ptr;

            Parallel.For(0, 10, ops.Lambda);
        }
    }

    unsafe class UnsafeOps {
        public float* p;
        public unsafe void Lambda(int value) {
            p[value] = value;
        }
    }

Looks to me like .NET 4 added some half-arsed attempt at disallowing fixed memory access in the compiler. In the above code block you could define UnsafeOps outside the fixed block and access the array after the fixed block. So it's not perfect...

Igor Zevaka
+1  A: 

The compiler is correct to reject that code. Fixed can only be used on local variables, and variables captured by a closure are not local variables, they are hoisted into the class used to maintain state for the closure.

Ben Voigt