7

我有以下代码:

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

这似乎需要做很多额外的工作才能让编译器变得“愚蠢”。

4

9 回答 9

8

我现在看到了根本问题。它比我最初想象的要深。基本上解决方案是在序列化之前修改表达式树,通过用常量节点替换所有不依赖于参数的子树。这显然被称为“功能化”。这里有一个解释

于 2008-09-21T09:39:54.803 回答
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,因此尚未对其进行测试,但通常,这就是我解决此类问题的方法。

于 2008-09-21T09:44:01.173 回答
1

Lambda 会自动“吸收”局部变量,恐怕这就是它们按定义工作的方式。

于 2008-09-21T08:28:32.227 回答
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);

某些前缀运算符会导致在构造匿名委托/函数之前评估变量的值。

于 2008-09-21T08:31:03.580 回答
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);
于 2008-09-21T08:38:59.940 回答
0

我现在遇到了问题:lambda 引用了可能不可序列化的包含类。然后做这样的事情:

public void static Func<string, string> MakePrependAction(String prefix){
    return (x => prefix + x);
}

(注意 static 关键字。)然后 lambda 不需要引用包含类。

于 2008-09-21T09:11:07.837 回答
-1

那这个呢

string prefix = "OLD:";
string _prefix=prefix;
Func<string, string> prependAction = (x => _prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));
于 2008-09-21T08:26:47.490 回答
-1

怎么样:

string prefix = "OLD:";
string prefixCopy = prefix;
Func<string, string> prependAction = (x => prefixCopy + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

?

于 2008-09-21T08:27:28.423 回答
-1

好吧,如果我们要在这里谈论“问题”,lambdas 来自函数式编程世界,在纯粹的函数式编程语言中,没有赋值,所以你的问题永远不会出现,因为前缀的值永远不会改变。我知道 C# 认为从函数式程序中导入想法很酷(因为 FP很酷!)但是很难让它变得漂亮,因为 C# 是并且将永远是一种命令式编程语言。

于 2008-09-21T08:48:09.247 回答