防止.NET"提升"局部变量
我有以下代码:
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);
我需要这样做的原因是我想序列化生成的委托。如果prefix变量在不可序列化的类中,则上述函数将不会序列化。
目前,我能看到的唯一解决方法是创建一个新的可序列化类,该类将字符串存储为成员变量并具有字符串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; } }
要使编译器变得"愚蠢",这似乎需要做很多额外的工作。
解决方案
那这个呢
string prefix = "OLD:"; string _prefix=prefix; Func<string, string> prependAction = (x => _prefix + x); prefix = "NEW:"; Console.WriteLine(prependAction("brownie"));
怎么样:
string prefix = "OLD:"; string prefixCopy = prefix; Func<string, string> prependAction = (x => prefixCopy + x); prefix = "NEW:"; Console.WriteLine(prependAction("brownie"));
?
Lambdas自动"吸入"局部变量,恐怕这只是它们按定义的方式工作。
这是一个非常普遍的问题,即变量被闭包无意修改,下面是一个简单得多的解决方案:
string prefix = "OLD:"; var actionPrefix = prefix; Func<string, string> prependAction = (x => actionPrefix + x); prefix = "NEW:"; Console.WriteLine(prependAction("brownie"));
如果我们使用的是reshaper,它将实际上识别出代码中可能引起意外副作用的风险的位置,因此,如果文件为"全绿色",则代码应该可以。
我认为从某些方面来说,如果我们有一些语法糖来处理这种情况会很好,所以我们可以将其写成单行代码,即
Func<string, string> prependAction = (x => ~prefix + x);
在构造匿名委托/函数之前,一些前缀运算符将导致对变量的值进行评估。
这里已经有几个答案,说明了如何避免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);
好吧,如果我们在这里要讨论"问题",那么lambda来自函数式编程领域,在纯函数式编程语言中,没有赋值,因此问题将永远不会出现,因为前缀的值永远不会改变。我知道Cthinks从功能程序中导入想法是很酷的(因为FP很酷!),但是很难使它变得漂亮,因为Cis和始终是命令式编程语言。
我现在遇到了问题:lambda引用了包含类,该类可能无法序列化。然后执行以下操作:
public void static Func<string, string> MakePrependAction(String prefix){ return (x => prefix + x); }
(请注意static关键字。)然后,lambda不需要引用包含的类。
我现在看到了潜在的问题。比我最初想的要深。基本上,解决方案是在将表达式树序列化之前修改表达式树,方法是将所有不依赖参数的子树替换为常量节点。显然,这被称为"集邦化"。
这里有一个解释。
只是再封闭一次...
说,类似:
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,因此尚未进行测试,但是通常情况下,这就是我解决此类问题的方法。