Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
474 views
in Technique[技术] by (71.8m points)

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

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...