我有以下代码:
string prefix = "OLD:";
Func<string, string> prependAction = (x => prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));
因为编译器用闭包替换了前缀变量,所以“NEW:brownie”被打印到控制台。
有没有一种简单的方法可以防止编译器提升前缀变量,同时仍然使用 lambda 表达式? 我想要一种使我的 Func 工作方式与以下内容相同的方法:
Func<string, string> prependAction = (x => "OLD:" + x);
我需要这个的原因是我想序列化生成的委托。 如果前缀变量位于不可序列化的类中,则上述函数将不会序列化。
目前我能看到的解决这个问题的唯一方法是创建一个新的可序列化类,它将字符串存储为成员变量并具有字符串前置方法:
string prefix = "NEW:";
var prepender = new Prepender {Prefix = prefix};
Func<string, string> prependAction = prepender.Prepend;
prefix = "OLD:";
Console.WriteLine(prependAction("brownie"));
使用辅助类:
[Serializable]
public class Prepender
{
public string Prefix { get; set; }
public string Prepend(string str)
{
return Prefix + str;
}
}
这似乎需要大量额外的工作才能让编译器变得“愚蠢”。
I have the following code:
string prefix = "OLD:";
Func<string, string> prependAction = (x => prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));
Because the compiler replaces the prefix variable with a closure "NEW:brownie" is printed to the console.
Is there an easy way to prevent the compiler from lifting the prefix variable whilst still making use of a lambda expression? I would like a way of making my Func work identically to:
Func<string, string> prependAction = (x => "OLD:" + x);
The reason I need this is I would like to serialize the resulting delegate. If the prefix variable is in a non-serializable class the above function will not serialize.
The only way around this I can see at the moment is to create a new serializable class that stores the string as a member variable and has the string prepend method:
string prefix = "NEW:";
var prepender = new Prepender {Prefix = prefix};
Func<string, string> prependAction = prepender.Prepend;
prefix = "OLD:";
Console.WriteLine(prependAction("brownie"));
With helper class:
[Serializable]
public class Prepender
{
public string Prefix { get; set; }
public string Prepend(string str)
{
return Prefix + str;
}
}
This seems like a lot of extra work to get the compiler to be "dumb".
发布评论
评论(9)
我现在看到了根本问题。 它比我最初想象的要深。 基本上,解决方案是在序列化表达式树之前对其进行修改,方法是将所有不依赖于参数的子树替换为常量节点。 这显然被称为“功能化”。
有一个解释此处。
I see the underlying problem now. It is deeper than I first thought. Basically the solution is to modify the expression tree before serializing it, by replacing all subtrees that do not depend on the parameters with constant nodes. This is apparently called "funcletization".
There is an explanation of it here.
只需再做一次关闭...
比如说:
尚未测试它,因为我目前无法访问 VS,但通常情况下,这就是我解决此类问题的方法。
Just make another closure...
Say, something like:
Havn't tested it yet as I don't have access to VS at the moment, but normally, this is how I solve such problem.
Lambda 会自动“吸收”局部变量,恐怕这就是它们定义的工作原理。
Lambdas automatically 'suck' in local variables, I'm afraid that's simply how they work by definition.
这是一个非常常见的问题,即变量被闭包无意中修改 - 一个更简单的解决方案就是:
如果您使用 resharper,它实际上会识别代码中可能导致意外副作用的位置像这样 - 所以如果文件是“全绿色”,你的代码应该没问题。
我认为在某些方面,如果我们有一些语法糖来处理这种情况,那就太好了,这样我们就可以将其编写为单行代码,即
某些前缀运算符会导致在构造匿名委托之前评估变量的值/功能。
This is a pretty common problem i.e. variables being modified by a closure unintentionally - a far simpler solution is just to go:
If you're using resharper it will actually identify the places in your code where you're at risk of causing unexpected side effects such as this - so if the file is "all green" your code should be OK.
I think in some ways it would have been nice if we had some syntactic sugar to handle this situation so we could have written it as a one-liner i.e.
Where some prefix operator would cause the variable's value to be evaluated prior to constructing the anonymous delegate/function.
这里已经有几个答案解释了如何避免 lambda“提升”变量。 不幸的是,这并不能解决您的根本问题。 无法序列化 lambda 与 lambda 已“提升”变量无关。 如果 lambda 表达式需要非序列化类的实例来计算,那么它无法序列化是完全合理的。
根据您实际想要执行的操作(我无法从您的帖子中完全决定),解决方案是将 lambda 的不可序列化部分移到外部。
例如,不要使用:
使用:
There are already several answers here explaining how you can avoid the lambda "lifting" your variable. Unfortunately that does not solve your underlying problem. Being unable to serialize the lambda has nothing to do with the lambda having "lifted" your variable. If the lambda expression needs an instance of a non-serialize class to compute, it makes perfect sense that it cannot be serialized.
Depending on what you actually are trying to do (I can't quite decide from your post), a solution would be to move the non-serializable part of the lambda outside.
For example, instead of:
use:
我现在遇到问题:lambda 引用可能不可序列化的包含类。 然后执行如下操作:(
注意 static 关键字。)然后 lambda 不需要引用包含的类。
I get the problem now: the lambda refers to the containing class which might not be serializable. Then do something like this:
(Note the static keyword.) Then the lambda needs not reference the containing class.
那这个呢
What about this
怎么样:
?
How about:
?
好吧,如果我们要在这里讨论“问题”,lambda 来自函数式编程世界,在纯函数式编程语言中,没有赋值,所以你的问题永远不会出现,因为前缀价值永远不会改变。 我知道 C# 认为从函数式程序中导入想法很酷(因为 FP 很酷!),但是很难让它变得漂亮,因为 C# 是并且永远是一种命令式编程语言。
Well, if we're gonna talk about "problems" here, lambdas come from the functional programming world, and in a purely functional programming langauge, there are no assignments and so your problem would never arise because prefix's value could never change. I understand C# thinks it's cool to import ideas from functional programs (because FP is cool!) but it's very hard to make it pretty, because C# is and will always be an imperative programming language.