C#不能在匿名方法体内使用ref或out参数
我试图创build一个函数,可以创build一个操作,增加任何整数传入英寸然而,我的第一次尝试是给我一个错误“不能在一个匿名方法体内使用ref或out参数”。
public static class IntEx { public static Action CreateIncrementer(ref int reference) { return () => { reference += 1; }; } }
我明白为什么编译器不喜欢这个,但我想有一个优雅的方式来提供一个很好的增量工厂,可以指向任何整数。 我看到要做到这一点的唯一方法就像下面这样:
public static class IntEx { public static Action CreateIncrementer(Func<int> getter, Action<int> setter) { return () => setter(getter() + 1); } }
但是,这对于主叫方来说当然是一种痛苦。 要求调用者创build两个lambdaexpression式,而不是只传入一个引用。 有没有更优雅的方式来提供这种function,或者我将只需要住两个lambda选项?
好吧,我已经发现,如果在不安全的上下文中,指针实际上是可能的:
public static class IntEx { unsafe public static Action CreateIncrementer(int* reference) { return () => { *reference += 1; }; } }
但是,垃圾收集器可能会在垃圾回收期间通过移动引用而造成严重破坏,如下所示:
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! } }
可以通过将variables固定到内存中的特定位置来解决此问题。 这可以通过将以下内容添加到构造函数来完成:
public Program() { GCHandle.Alloc(_i, GCHandleType.Pinned); }
这使得垃圾收集器不能移动对象,所以我们正在寻找。 然而,那么你必须添加一个析构函数来释放这个pin,并且在整个对象的生命周期中它会将内存碎片化。 不是很容易。 这在C ++中会更有意义,C ++中的东西不会被移动,资源pipe理就是当然,但是在C#中并不是那么简单,所有这些都是自动的。
所以看起来像故事的道理是,只是将该成员int包装在引用types中,并且完成它。
(是的,这就是我问这个问题之前的工作方式,但只是想知道是否有办法摆脱所有的Reference <int>成员variables,只是使用常规的整数。 )
这不可能。
编译器会将匿名方法使用的所有局部variables和参数转换为自动生成的闭包类中的字段。
CLR不允许ref
types被存储在字段中。
例如,如果您在本地variables中传递值types作为这样的ref
参数,则该值的生存期将超出其堆栈框架。
对于运行时来说,这可能是一个有用的function,允许创build具有机制的variables引用来防止其持久性; 这样的function可以让索引器的行为像一个数组(例如,所以一个Dictionary <Int32,Point>可以通过“myDictionary [5] .X = 9;”来访问)。 我认为如果这样的引用不能被向下转换为其他types的对象,也不能被用作字段,也不能被引用自己传递(因为任何地方这样的引用可能被存储在参考之前就会超出范围本身会)。 不幸的是,CLR不提供这样的function。
要实现你所要做的事情,需要在闭包中使用引用参数的任何函数的调用者必须在闭包中包含它想要传递给这个函数的任何variables。 如果有一个特殊的声明来表明一个参数将以这种方式被使用,编译器可能会实现所需的行为。 也许在.net 5.0编译器,但我不知道这将是多么有用。
顺便说一下,我的理解是,Java中的闭包使用了按值的语义,而.net中的闭包是通过引用。 我可以理解一些偶然使用的引用语义,但默认使用引用似乎是一个可疑的决定,类似于使用默认的by-reference参数 – 通过VB6传递VB版本的语义。 如果想创build一个委托来调用一个函数时捕获一个variables的值(例如,如果需要一个委托在委托创build时使用X的值调用MyFunction(X)),那么使用lambda额外的温度,或者是简单地使用一个委托工厂,而不是打扰Lambdaexpression式。