防止.NET“提升”局部变量

时间:2008-09-21 08:24:05

标签: c# .net lambda

我有以下代码:

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;
    }
}

这似乎是让编译器变得“愚蠢”的额外工作。

9 个答案:

答案 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#是并且将永远是命令式编程语言。

相关问题