匿名函数和局部变量

8

假设你的表单上有一个按钮。你在按钮的Click事件中附加了一个匿名函数:

void Test()
{
  int x = 10;
  btn.Click += (sender, e) => { MessageBox.Show(x.ToString()); };
}

这段代码按预期工作,显示10;这意味着它可以访问局部变量。我的问题是如何以及为什么?匿名函数如何访问局部上下文?
我实际处理的问题是,我需要将这个匿名函数升级(也就是说)为常规函数(事件处理程序)。但这样做意味着我将无法访问变量x。我也不能把它作为参数传递,因为那样我就无法将其附加到单击事件(签名不匹配)。我可以通过创建全局变量等来解决这个问题,但匿名函数如何使得访问其范围外的东西成为可能?

可能是 https://dev59.com/okfRa4cB1Zd3GeqP7DFF 的重复问题。 - Hamid Pourjam
你为什么需要创建一个常规函数呢?如果你能提供更多的上下文,那就更好了。例如,仅仅能够获取委托是否足够?因为你可以很容易地这样做:EventHandler handler = (sender, e) => { ... } - Jon Skeet
@JonSkeet:这是一个更大的问题的一部分,我的附加属性没有调用我声明并匿名分配的“PropertyChangedCallback”。我怀疑这可能是由于匿名方法超出范围并因此被GC擦除,所以想尝试使用常规函数。 - dotNET
@dotNET:不,那不会是问题。通过匿名函数创建的委托实例并不比通过常规函数创建的委托实例更快地被垃圾回收。使用匿名函数取消订阅事件存在一些微妙之处,但那是另一回事。 - Jon Skeet
1个回答

18

匿名函数的一半关键之处是它们可以捕获指定它们的上下文。能够这样做非常方便 - 这就是"为什么"。

编译器实现这一点的方式是在需要时创建一个新的类。因此,您的代码会转换成以下内容:

void Test()
{
    TestHelper helper = new TestHelper();
    helper.x = 10;

    btn.Click += helper.Method;
}

private class TestHelper
{
    public int x = 10;

    public void Method(object sender, EventArgs e)
    {
        MessageBox.Show(x.ToString());
    }
}

Test中每次使用x都将转换为相应实例的helper.x的使用。这也覆盖了具有不同生命周期的变量。例如,假设您有以下循环:

for (int i = 0; i < 10; i++)
{
    int x = i;
    // Use with some anonymous function
}

如果在循环内部声明TestHelper,则会为每次迭代创建一个新的实例......而如果x在循环外部声明,则只有一个实例,所有匿名函数都有效地共享。

当仅捕获this时,编译器会在现有类中创建一个实例方法,而不是创建一个帮助类。当存在具有潜在多个匿名函数的不同作用域来捕获各种变量时,事情可能会变得更加复杂,某些帮助类可能具有对其他帮助类实例的引用等。


TestHelper中的x与本地的x是如何关联的?也就是说,如果在捕获之后本地的x发生了变化,那么它会对TestHelper.x产生什么影响? - dotNET
@dotNET:看一下Test方法 - 不再有本地的x变量了。我会在答案中更加强调这一点。 - Jon Skeet
嗯嗯嗯...嗯嗯嗯嗯嗯...+1。 - dotNET

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接