代码之家  ›  专栏  ›  技术社区  ›  Brownie

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

  •  7
  • Brownie  · 技术社区  · 16 年前

    我有以下代码:

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

    因为编译器将前缀变量替换为一个闭包,所以将“new:brownie”打印到控制台。

    有没有一种简单的方法可以防止编译器在使用lambda表达式的同时提升前缀变量?我希望有一种方法可以使我的功能与以下功能完全相同:

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

    我之所以需要这样做,是因为我想序列化生成的委托。如果前缀变量位于不可序列化类中,则上述函数将不会序列化。

    目前我唯一能看到的解决方法是创建一个新的可序列化类,该类将字符串存储为成员变量,并具有string 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 回复  |  直到 11 年前
        1
  •  8
  •   Rasmus Faber    11 年前

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

        2
  •  2
  •   chakrit Dutchie432    16 年前

    再做一次闭幕…

    比如说:

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

        3
  •  1
  •   ljs TheVillageIdiot    16 年前

    lambda会自动“吸吮”局部变量,恐怕这只是它们定义的工作方式。

        4
  •  0
  •   Bittercoder    16 年前

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

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

    其中,某些前缀运算符将导致在构造匿名委托/函数之前对变量的值进行计算。

        5
  •  0
  •   Rasmus Faber    16 年前

    现在我发现问题了:lambda引用了可能无法序列化的包含类。然后这样做:

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

    (注意静态关键字。)那么lambda不需要引用包含类。

        6
  •  0
  •   Rasmus Faber    16 年前

    这里已经有几个答案解释了如何避免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);
    
        7
  •  -1
  •   T. Stone    16 年前

    这个怎么样?

    string prefix = "OLD:";
    string _prefix=prefix;
    Func<string, string> prependAction = (x => _prefix + x);
    prefix = "NEW:";
    Console.WriteLine(prependAction("brownie"));
    
        8
  •  -1
  •   Martijn    16 年前

    怎么样:

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

    ?

        9
  •  -1
  •   Martijn    16 年前

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