C#不能在匿名方法体内使用ref或out参数

Dax*_*ohl 41 c# lambda anonymous-methods ref

我正在尝试创建一个可以创建一个Action的函数来增加传入的整数.但是我的第一次尝试是给出了一个错误"不能在匿名方法体内使用ref或out参数".

public static class IntEx {
    public static Action CreateIncrementer(ref int reference) {
        return () => {
            reference += 1;
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

我理解为什么编译器不喜欢这个,但是我希望有一个优雅的方法来提供一个可以指向任何整数的漂亮的增量器工厂.我看到这样做的唯一方法如下:

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

但当然这对来电者来说更是一种痛苦; 要求调用者创建两个lambda而不是仅仅传入一个引用.有没有更优雅的方式来提供这个功能,或者我只需要忍受双lambda选项?

Dax*_*ohl 31

好的,我发现如果在不安全的环境中,指针实际上可能的:

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

但是,垃圾收集器可以通过在垃圾收集期间移动引用来破坏这种情况,如下所示:

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!
    }
}
Run Code Online (Sandbox Code Playgroud)

通过将变量固定到内存中的特定位置,可以解决此问题.这可以通过在构造函数中添加以下内容来完成:

    public Program() {
        GCHandle.Alloc(_i, GCHandleType.Pinned);
    }
Run Code Online (Sandbox Code Playgroud)

这样可以防止垃圾收集器移动物体,这正是我们所寻找的.然而,您必须添加一个析构函数来释放该引脚,并在整个对象的生命周期内对内存进行分段.不是更容易.这在C++中更有意义,在这里,东西不会被移动,资源管理也是如此,但在C#中并没有那么多,这应该是自动的.

所以看起来故事的寓意是,只需将该成员int包装在引用类型中并完成它.

(是的,这就是我在问这个问题之前让它工作的方式,但只是想弄清楚是否有一种方法可以摆脱所有的Reference <int>成员变量并且只使用常规的int.哦. )

  • +1使用不安全的上下文来管理模式的有趣解决方法. (4认同)
  • @Dax Fohl这是正确的 - 我认为现在在我们的两条评论之后更清楚了. (2认同)

SLa*_*aks 24

这是不可能的.

编译器会将匿名方法使用的所有局部变量和参数转换为自动生成的闭包类中的字段.

CLR不允许将ref类型存储在字段中.

例如,如果将局部变量中的值类型作为此类ref参数传递,则值的生命周期将超出其堆栈帧.