C# 无法在匿名方法体内使用 ref 或 out 参数
我正在尝试创建一个函数,该函数可以创建一个 Action,该 Action 会递增传入的任何整数。但是,我的第一次尝试给了我一个错误“不能在匿名方法体内使用 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);
}
}
但当然,这对于调用者来说使用起来更加痛苦;要求调用者创建两个 lambda,而不是仅传递引用。有没有更优雅的方式来提供此功能,或者我只需要接受两个 lambda 选项?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
好吧,我发现,如果在不安全的上下文中,指针实际上是可能的:
收集期间移动您的引用来对此造成严重破坏,如下所示:
但是,垃圾收集器可以通过在垃圾 通过将变量固定到内存中的特定位置来解决这个问题。这可以通过将以下内容添加到构造函数中来完成:
这可以防止垃圾收集器移动对象,这正是我们正在寻找的。但是,您必须添加一个析构函数来释放引脚,并且它会在对象的整个生命周期中产生内存碎片。确实不容易。这在 C++ 中更有意义,因为 C++ 中的内容不会四处移动,并且资源管理是理所当然的,但在 C# 中则不然,因为所有这些都应该是自动的。
所以看起来这个故事的寓意是,只需将该成员 int 包装在引用类型中即可完成。
(是的,这就是我在提出问题之前让它工作的方式,但只是想弄清楚是否有一种方法可以摆脱我所有的 Reference成员变量并只使用常规整数。哦,好吧。 )
Okay, I've found that it actually is possible with pointers if in unsafe context:
However, the garbage collector can wreak havoc with this by moving your reference during garbage collection, as the following indicates:
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:
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.)
这是不可能的。
编译器会将匿名方法使用的所有局部变量和参数转换为自动生成的闭包类中的字段。
CLR 不允许将
ref
类型存储在字段中。例如,如果您将局部变量中的值类型作为
ref
参数传递,则该值的生命周期将超出其堆栈帧。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.对于运行时来说,允许创建变量引用并具有防止其持久化的机制可能是一个有用的功能;这样的功能将允许索引器表现得像数组(例如,可以通过“myDictionary[5].X = 9;”访问Dictionary)。我认为,如果此类引用不能向下转换为其他类型的对象,也不能用作字段,也不能通过引用本身传递(因为任何可以存储此类引用的地方都会在引用之前超出范围),则可以安全地提供这样的功能本身会)。不幸的是,CLR 不提供这样的功能。
要实现您想要的功能,需要在闭包中使用引用参数的任何函数的调用者必须将它想要传递给此类函数的任何变量包装在闭包中。如果有一个特殊声明来指示将以这种方式使用参数,则编译器实现所需的行为可能是实用的。也许在 .net 5.0 编译器中,尽管我不确定这有多大用处。
顺便说一句,我的理解是 Java 中的闭包使用按值语义,而 .net 中的闭包是按引用。我可以理解按引用语义的一些偶尔用途,但默认使用引用似乎是一个可疑的决定,类似于从 VB 版本到 VB6 使用默认的按引用参数传递语义。如果希望在创建委托来调用函数时捕获变量的值(例如,如果希望委托在创建委托时使用 X 的值来调用 MyFunction(X)),那么使用 lambda 是否更好使用额外的临时变量,还是直接使用委托工厂而不用 Lambda 表达式会更好。
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.