我有以下代码:
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);
我需要这个的原因是我想序列化生成的委托。如果前缀变量在非序列化类中,则上述函数不会序列化。
目前唯一可以看到的方法是创建一个新的可序列化类,它将字符串存储为成员变量并具有字符串prepend方法:
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;
}
}
这似乎是让编译器变得“愚蠢”的额外工作。
答案 0 :(得分:8)
我现在看到了潜在的问题。它比我想象的要深。基本上,解决方案是在序列化之前修改表达式树,方法是将所有不依赖于参数的子树替换为常量节点。这显然被称为“funcletization”。 有一个解释here。
答案 1 :(得分:2)
再做一次关闭......
说,像:
var prepend = "OLD:";
Func<string, Func<string, string>> makePrepender = x => y => (x + y);
Func<string, string> oldPrepend = makePrepender(prepend);
prepend = "NEW:";
Console.WriteLine(oldPrepend("Brownie"));
尚未测试它,因为我此刻无法访问VS,但通常情况下,这就是我解决此问题的方法。
答案 2 :(得分:1)
Lambdas自动“吮吸”局部变量,我担心它们只是按照定义工作。
答案 3 :(得分:0)
这是一个非常常见的问题,即无意中通过闭包修改变量 - 一个更简单的解决方案就是:
string prefix = "OLD:";
var actionPrefix = prefix;
Func<string, string> prependAction = (x => actionPrefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));
如果您正在使用resharper,它实际上会识别代码中您可能会导致意外副作用的地方 - 例如,如果文件是“全绿”,则代码应该没问题。
我认为在某些方面如果我们有一些语法糖来处理这种情况会很好,所以我们可以把它写成一个单行,即
Func<string, string> prependAction = (x => ~prefix + x);
在构造匿名委托/函数之前,某些前缀运算符会导致对变量的值进行求值。
答案 4 :(得分:0)
这里已经有几个答案解释了如何避免lambda“提升”你的变量。不幸的是,这并没有解决您的潜在问题。无法序列化lambda与lambda“提升”你的变量无关。如果lambda表达式需要一个非序列化类的实例来计算,那么它就不能被序列化了。
根据您实际尝试做的事情(我无法从您的帖子中做出决定),解决方案是将lambda的非序列化部分移到外面。
例如,而不是:
NonSerializable nonSerializable = new NonSerializable();
Func<string, string> prependAction = (x => nonSerializable.ToString() + x);
使用:
NonSerializable nonSerializable = new NonSerializable();
string prefix = nonSerializable.ToString();
Func<string, string> prependAction = (x => prefix + x);
答案 5 :(得分:0)
我现在遇到问题:lambda引用了可能无法序列化的包含类。然后做这样的事情:
public void static Func<string, string> MakePrependAction(String prefix){
return (x => prefix + x);
}
(注意static关键字。)然后lambda不需要引用包含的类。
答案 6 :(得分:-1)
这个怎么样?
string prefix = "OLD:";
string _prefix=prefix;
Func<string, string> prependAction = (x => _prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));
答案 7 :(得分:-1)
怎么样:
string prefix = "OLD:";
string prefixCopy = prefix;
Func<string, string> prependAction = (x => prefixCopy + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));
答案 8 :(得分:-1)
好吧,如果我们要在这里讨论“问题”,lambdas来自函数式编程世界,并且在一个纯函数式编程语言中,没有赋值,所以你的问题永远不会因为前缀的值永远不会改变而出现。我理解C#认为从功能程序中导入想法很酷(因为FP 很酷!)但很难让它变得漂亮,因为C#是并且将永远是命令式编程语言。