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

发布于 2024-07-05 08:26:31 字数 999 浏览 4 评论 0 原文

我有以下代码:

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

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

I have the following code:

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

Because the compiler replaces the prefix variable with a closure "NEW:brownie" is printed to the console.

Is there an easy way to prevent the compiler from lifting the prefix variable whilst still making use of a lambda expression? I would like a way of making my Func work identically to:

Func<string, string> prependAction = (x => "OLD:" + x);

The reason I need this is I would like to serialize the resulting delegate. If the prefix variable is in a non-serializable class the above function will not serialize.

The only way around this I can see at the moment is to create a new serializable class that stores the string as a member variable and has the string prepend method:

string prefix = "NEW:";
var prepender = new Prepender {Prefix = prefix};
Func<string, string> prependAction = prepender.Prepend;
prefix = "OLD:";
Console.WriteLine(prependAction("brownie"));

With helper class:

[Serializable]
public class Prepender
{
    public string Prefix { get; set; }
    public string Prepend(string str)
    {
        return Prefix + str;
    }
}

This seems like a lot of extra work to get the compiler to be "dumb".

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

蓝咒 2024-07-12 08:26:31

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

I see the underlying problem now. It is deeper than I first thought. Basically the solution is to modify the expression tree before serializing it, by replacing all subtrees that do not depend on the parameters with constant nodes. This is apparently called "funcletization".
There is an explanation of it here.

热情消退 2024-07-12 08:26:31

只需再做一次关闭...

比如说:

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

Just make another closure...

Say, something like:

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"));

Havn't tested it yet as I don't have access to VS at the moment, but normally, this is how I solve such problem.

秋叶绚丽 2024-07-12 08:26:31

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

Lambdas automatically 'suck' in local variables, I'm afraid that's simply how they work by definition.

莳間冲淡了誓言ζ 2024-07-12 08:26:31

这是一个非常常见的问题,即变量被闭包无意中修改 - 一个更简单的解决方案就是:

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

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

This is a pretty common problem i.e. variables being modified by a closure unintentionally - a far simpler solution is just to go:

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

If you're using resharper it will actually identify the places in your code where you're at risk of causing unexpected side effects such as this - so if the file is "all green" your code should be OK.

I think in some ways it would have been nice if we had some syntactic sugar to handle this situation so we could have written it as a one-liner i.e.

Func<string, string> prependAction = (x => ~prefix + x);

Where some prefix operator would cause the variable's value to be evaluated prior to constructing the anonymous delegate/function.

心如狂蝶 2024-07-12 08:26:31

这里已经有几个答案解释了如何避免 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);

There are already several answers here explaining how you can avoid the lambda "lifting" your variable. Unfortunately that does not solve your underlying problem. Being unable to serialize the lambda has nothing to do with the lambda having "lifted" your variable. If the lambda expression needs an instance of a non-serialize class to compute, it makes perfect sense that it cannot be serialized.

Depending on what you actually are trying to do (I can't quite decide from your post), a solution would be to move the non-serializable part of the lambda outside.

For example, instead of:

NonSerializable nonSerializable = new NonSerializable();
Func<string, string> prependAction = (x => nonSerializable.ToString() + x);

use:

NonSerializable nonSerializable = new NonSerializable();
string prefix = nonSerializable.ToString();
Func<string, string> prependAction = (x => prefix + x);
晚雾 2024-07-12 08:26:31

我现在遇到问题:lambda 引用可能不可序列化的包含类。 然后执行如下操作:(

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

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

I get the problem now: the lambda refers to the containing class which might not be serializable. Then do something like this:

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

(Note the static keyword.) Then the lambda needs not reference the containing class.

糖果控 2024-07-12 08:26:31

那这个呢

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

What about this

string prefix = "OLD:";
string _prefix=prefix;
Func<string, string> prependAction = (x => _prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));
星星的轨迹 2024-07-12 08:26:31

怎么样:

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

How about:

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

?

冷情妓 2024-07-12 08:26:31

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

Well, if we're gonna talk about "problems" here, lambdas come from the functional programming world, and in a purely functional programming langauge, there are no assignments and so your problem would never arise because prefix's value could never change. I understand C# thinks it's cool to import ideas from functional programs (because FP is cool!) but it's very hard to make it pretty, because C# is and will always be an imperative programming language.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文