防止.NET"提升"局部变量

时间:2020-03-06 14:30:09  来源:igfitidea点击:

我有以下代码:

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,因此尚未进行测试,但是通常情况下,这就是我解决此类问题的方法。