22

我有如下代码:

struct A
{
    void SomeMethod()
    {
        var items = Enumerable.Range(0, 10).Where(i => i == _field);
    }

    int _field;
}

...然后我得到以下编译器错误:

结构中的匿名方法不能访问“this”的实例成员。

任何人都可以解释这里发生了什么。

4

2 回答 2

17

通过引用捕获变量(即使它们实际上是值类型;然后进行装箱)。

但是,this在 ValueType (struct) 中不能装箱,因此您无法捕获它。

Eric Lippert 有一篇关于捕获 ValueTypes 的惊喜的好文章。让我找到链接


回应 Chris Sinclair 的评论请注意:

作为快速修复,您可以将结构存储在局部变量中:A thisA = this; var items = Enumerable.Range(0, 10).Where(i => i == thisA._field);Chris Sinclair 4 分钟前

请注意,这会产生令人惊讶的情况:的身份不同。更明确地说,如果您选择将lambda保留更长时间,它将通过引用捕获盒装副本,而不是调用的实际实例。thisAthis thisASomeMethod

于 2012-10-09T13:47:15.693 回答
3

当您有一个匿名方法时,它将被编译成一个新类,该类将有一个方法(您定义的那个)。它还将引用您使用的超出匿名方法范围的每个变量。重要的是要强调它是该变量的引用,而不是副本。俗话说“lambdas 关闭变量,而不是值”。这意味着,如果您关闭 lambda 范围之外的变量,然后在定义匿名方法之后(但在调用它之前)更改该变量,那么您将在调用它时看到更改的值)。

那么,这一切的意义何在。好吧,如果您要关闭this一个结构,它是一种值类型,那么 lambda 有可能比结构更长寿。匿名方法将在一个中,而不是在一个结构中,所以它会在堆上继续存在,只要它需要,你可以自由地传递对该类的引用(直接或间接)你想要的任何地方。

现在假设我们有一个局部变量,它具有您在此处定义的类型的结构。我们使用这个命名方法来生成一个 lambda,让我们暂时假设items返回了查询(而不是方法void)。然后将可以将该查询存储在另一个实例(而不是本地)变量中,并稍后在另一个方法上迭代该查询。这里会发生什么?本质上,一旦它不再在范围内,我们就会保留对堆栈上的值类型的引用。

这意味着什么?答案是,我们不知道。(请查看链接;这有点像我的论点的症结所在。)数据可能恰好相同,可能已被清零,可能已被完全不同的对象填充,无法知道。作为一门语言,C# 竭尽全力阻止你做这样的事情。诸如 C 或 C++ 之类的语言并不会那么努力地阻止您自己动手。

Now, in this particular case, it's possible that you aren't going to use the lambda outside of the scope of what this refers to, but the compiler doesn't know that, and if it lets you create the lambda it has no way of determining whether or not you expose it in a way that could result in it outliving this, so the only way to prevent this problem is to disallow some cases that aren't actually problematic.

于 2012-10-09T15:43:05.080 回答