C# Cannot use ref or out parameter inside an anonymous method body

19,271

Solution 1

Okay, I've found that it actually is possible with pointers if in unsafe context:

public static class IntEx {
    unsafe public static Action CreateIncrementer(int* reference) {
        return () => {
            *reference += 1;
        };
    }
}

However, the garbage collector can wreak havoc with this by moving your reference during garbage collection, as the following indicates:

class Program {
    static void Main() {
        new Program().Run();
        Console.ReadLine();
    }

    int _i = 0;
    public unsafe void Run() {
        Action incr;
        fixed (int* p_i = &_i) {
            incr = IntEx.CreateIncrementer(p_i);
        }
        incr();
        Console.WriteLine(_i); // Yay, incremented to 1!
        GC.Collect();
        incr();
        Console.WriteLine(_i); // Uh-oh, still 1!
    }
}

One can get around this problem by pinning the variable to a specific spot in memory. This can be done by adding the following to the constructor:

    public Program() {
        GCHandle.Alloc(_i, GCHandleType.Pinned);
    }

That keeps the garbage collector from moving the object around, so exactly what we're looking for. However then you've got to add a destructor to release the pin, and it fragments the memory throughout the lifetime of the object. Not really any easier. This would make more sense in C++, where stuff doesn't get moved around, and resource management is par the course, but not so much in C# where all that is supposed to be automatic.

So looks like the moral of the story is, just wrap that member int in a reference type and be done with it.

(And yes, that's the way I had it working before asking the question, but was just trying to figure out if there was a way I could get rid of all my Reference<int> member variables and just use regular ints. Oh well.)

Solution 2

This is not possible.

The compiler will transform all local variables and parameters used by anonymous methods into fields in an automatically generated closure class.

The CLR does not allow ref types to be stored in fields.

For example, if you pass a value type in a local variable as such a ref parameter, the value's lifetime would extend beyond its stack frame.

Solution 3

It might have been a useful feature for the runtime to allow the creation of variable references with a mechanism to prevent their persistence; such a feature would have allowed an indexer to behave like an array (e.g. so a Dictionary<Int32, Point> could be accessed via "myDictionary[5].X = 9;"). I think such a feature could have been provided safely if such references could not be downcast to other types of objects, nor used as fields, nor passed by reference themselves (since anyplace such a reference could be stored would go out of scope before the reference itself would). Unfortunately, the CLR does not provide such a feature.

To implement what you're after would require that the caller of any function which uses a reference parameter within a closure must wrap within a closure any variable it wants to pass to such a function. If there were a special declaration to indicate that a parameter would be used in such a fashion, it might be practical for a compiler to implement the required behavior. Maybe in a .net 5.0 compiler, though I'm not sure how useful that would be.

BTW, my understanding is that closures in Java use by-value semantics, while those in .net are by-reference. I can understand some occasional uses for by-reference semantics, but using reference by default seems a dubious decision, analogous to the use of default by-reference parameter-passing semantics for VB versions up through VB6. If one wants to capture the value of a variable when creating a delegate to call a function (e.g. if one wants a delegate to call MyFunction(X) using the value of X when the delegate is created), is it better to use a lambda with an extra temp, or is it better to simply use a delegate factory and not bother with Lambda expressions.

Share:
19,271
Dax Fohl
Author by

Dax Fohl

My about me is no longer blank.

Updated on June 29, 2022

Comments

  • Dax Fohl
    Dax Fohl almost 2 years

    I'm trying to create a function that can create an Action that increments whatever integer is passed in. However my first attempt is giving me an error "cannot use ref or out parameter inside an anonymous method body".

    public static class IntEx {
        public static Action CreateIncrementer(ref int reference) {
            return () => {
                reference += 1;
            };
        }
    }
    

    I understand why the compiler doesn't like this, but nonetheless I'd like to have a graceful way to provide a nice incrementer factory that can point to any integer. The only way I'm seeing to do this is something like the following:

    public static class IntEx {
        public static Action CreateIncrementer(Func<int> getter, Action<int> setter) {
            return () => setter(getter() + 1);
        }
    }
    

    But of course that is more of a pain for the caller to use; requiring the caller to create two lambdas instead of just passing in a reference. Is there any more graceful way of providing this functionality, or will I just have to live with the two-lambda option?

  • Dax Fohl
    Dax Fohl over 13 years
    Yeah, Eric Lippert wrote a blog about that. blogs.msdn.com/b/ericlippert/archive/2009/11/12/…. I actually do have code that depends on this behavior; keeping a framecount for OpenGL loop without needing a framecount member variable for instance. I like the C++ spec better that allows either semantic.
  • John K
    John K over 13 years
    +1 for interesting workaround of managed mode using unsafe context.
  • supercat
    supercat over 13 years
    @Dax: There are cases where pass-by-reference semantics are necessary, certainly. The same is true, however, of parameter passing. That doesn't imply that pass-by-reference should be the default. BTW, I wonder what the pros and cons would have been of replacing by-reference closure variables with single-element arrays, allowing different classes to be created for anonymous methods that need different combinations of closure variables (avoiding some GC issues).
  • Mr. TA
    Mr. TA almost 12 years
    No need for GCHandle.Alloc - just extend the fixed statement curly braces.
  • Dax Fohl
    Dax Fohl almost 12 years
    @Mr.TA That was just an example to show that the GC could mess things up. Any worthwhile use of the incr function would probably return it from the method that creates it, or add it to a persistent object as a member variable, which obviously leaves the "fixed" scope.
  • Mr. TA
    Mr. TA almost 12 years
    @Dax Fohl that's correct - I think it's clearer now after both of our comments.