【问题标题】:C# closures behavior changes if a instantiate the object before versus inside the lambda?如果在 lambda 之前实例化对象而不是在 lambda 内部实例化对象,C# 闭包行为会发生变化吗?
【发布时间】:2021-04-30 23:10:32
【问题描述】:

所以我发现了这种奇怪之处,我的代码行为会根据我在 lambda 函数中“关闭”的对象的分配位置而改变。

这是我所经历的一个简化示例


Example01(){
    var myCounterLocalInstance = new MyCounter(); 
    Action<string> lambdaFunction = (args) => IncrementAndPrint(args, myCounterLocalInstance);
}

Example02(){
   Action<string> lambdaFunction = (args) => IncrementAndPrint(args, new MyCounter());
}

IncrementAndPrint(string args, MyCounter counter){
   Console.WriteLine(args + counter.GetValueAndCount());
}

class MyCounter
{
   int _counter;
   public int GetValueAndCount() => _counter++;
}

奇怪的是 Example01 和 Example02 在我的案例中实际上并没有给出相同的结果。在 Example01 中,一切都按预期工作,如果 args == "cat":

  1. 日志:“cat0”
  2. 日志:“cat1”
  3. 日志:“cat2”

这是预期行为,因为 MyCounter 是有状态的。 但是,Example02 会给出以下输出:

  1. 日志:“cat0”
  2. 日志:“cat0”
  3. 日志:“cat0”

每次调用该方法时都会重置 _counter 成员变量...这就像 myCounter 是按值传递的,并且每次在 Example02 中使用时都会重新实例化,而不是像第一个示例中那样放入闭包对象中 - - 像预期的那样。

对此有什么解释吗?我猜这是已知行为而不是错误?一定有一些关于闭包的东西我不知道。这让我非常头疼,因为我从没想过行为会根据我分配闭包参数的位置而改变。

谢谢!

【问题讨论】:

  • 你的代码不完整,action在哪里调用? lambdaFunction = (args) =&gt; IncrementAndPrint(args, new MyCounter()); 所以每次调用都会创建一个新实例。我不知道你为什么觉得这令人惊讶....
  • 我真正关心的是为什么你认为它们应该是一样的。每次调用 lambdaFunction 时,您要么在 (Example01) 中创建一个实例,要么创建一个新实例 (Example02)... 可以看到为什么即使您只是静态调用此方法也会得到不同的结果? IncrementAndPrint("cat", myCounterLocalInstance)IncrementAndPrint("cat", new MyCounter());
  • new MyCounter() 在每次调用时被调用。每次都会有一个新的。
  • 嗯好吧,这是有道理的。我只是认为,当您创建一个 lambda 时,它最初会将其所有依赖项作为成员存储在一个对象中,然后将它们传递给您放入的任何方法。但是,当然它不仅仅是存储成员和一个函数,它正在创建一个全新的功能。我想我只是忘记了它可以做到这一点,在我使用它们来重载一个方法的情况下。谢谢!

标签: c# lambda closures parameter-passing


【解决方案1】:

你想多了。

在此方法中,您将创建一个 MyCounter 实例,每次调用委托时都会递增。

static void Example01(){
   var myCounterLocalInstance = new MyCounter(); 
   Action<string> lambdaFunction = (args) => IncrementAndPrint(args, myCounterLocalInstance);
   lambdaFunction("cat");
   lambdaFunction("cat");
   lambdaFunction("cat");
   lambdaFunction("cat");
}

在此方法中,您每次调用委托时都会创建一个新实例 MyCounter,其计数将始终为 0!

static void Example02(){
   Action<string> lambdaFunction = (args) => IncrementAndPrint(args, new MyCounter());
   lambdaFunction("cat");
   lambdaFunction("cat");
   lambdaFunction("cat");
   lambdaFunction("cat");
}

如果你只是静态调用IncrementAndPrint,你会得到相同的结果:

static void Example01(){
   var myCounterLocalInstance = new MyCounter();
   IncrementAndPrint("cat",myCounterLocalInstance);
   IncrementAndPrint("cat",myCounterLocalInstance);
   IncrementAndPrint("cat",myCounterLocalInstance);
   IncrementAndPrint("cat",myCounterLocalInstance);
}

static void Example02(){
   IncrementAndPrint("cat", new MyCounter());
   IncrementAndPrint("cat", new MyCounter());
   IncrementAndPrint("cat", new MyCounter());
   IncrementAndPrint("cat", new MyCounter());
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    相关资源
    最近更新 更多