如果 在 lambda 之前和在 lambda 内部实例化对象,则 C# 闭包行为会发生变化?

C# closures behavior changes if a instantiate the object before versus inside the lambda?

提问人:Charly 提问时间:1/27/2021 更新时间:1/29/2021 访问量:70

问:

因此,我发现了一种奇怪的现象,即我的代码的行为会根据我在 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 中使用时都会被重新实例化,而不是像第一个示例中那样被放入闭包对象中——就像预期的那样。

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

谢谢!

C# Lambda 闭包参数 传递

评论

1赞 Jeremy Lakeman 1/27/2021
您的代码不完整,在哪里调用操作? 因此,每次调用都会创建一个新实例。我不知道你为什么觉得这令人惊讶......lambdaFunction = (args) => IncrementAndPrint(args, new MyCounter());
4赞 TheGeneral 1/27/2021
我真正关心的是为什么你认为它们应该是一样的。每次调用时,您要么在 () 中有一个实例,要么创建一个新实例 ()...可以理解为什么即使您只是静态调用此方法也会得到不同的结果吗? 或lambdaFunctionExample01Example02IncrementAndPrint("cat", myCounterLocalInstance)IncrementAndPrint("cat", new MyCounter());
1赞 mjwills 1/27/2021
new MyCounter()在每次调用时被调用。它每次都会得到一个新的。
0赞 Charly 1/27/2021
嗯,好吧,这是有道理的。我只是认为,当你创建一个 lambda 时,它最初会将它的所有依赖项存储为对象中的成员,然后将它们传递给你输入的任何方法。但是,是的,当然,它不仅仅是存储成员和函数,而是创建一个全新的函数。我想我只是忘记了它可以做到这一点,在我使用它们来重载方法的上下文中。谢谢!

答:

1赞 TheGeneral 1/27/2021 #1

你想多了。

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

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

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

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