为什么结构体内的匿名方法无法访问'this'的实例成员

时间:2012-10-09 13:42:37

标签: c#

我有以下代码:

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

    int _field;
}

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

  

结构体内的匿名方法无法访问“this”的实例成员。

有人可以解释这里发生了什么。

2 个答案:

答案 0 :(得分:15)

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

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

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


请回应Chris Sinclair的评论:

  

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

请注意这会产生令人惊讶的情况:thisA身份 this相同。更明确地说,如果您选择将 lambda 保持更长时间,则会通过引用捕获带框的副本 thisA,并且调用SomeMethod的实际实例。

答案 1 :(得分:1)

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

所以,所有这一切的重点是什么。好吧,如果你要关闭一个结构的this,这是一个值类型,lambda可能比结构更长。匿名方法将在中,而不是结构,因此它将在堆上运行,只要它需要,并且您可以自由地传递对该类的引用(直接或间接的)无论你想要什么。

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

这是什么意思?答案是we have no idea。 (请查看链接;这是我论证的关键。)数据可能恰好相同,可能已被清零,可能由完全不同的对象填充,无法知道。作为一种语言,C#不遗余力地阻止你做这样的事情。像C或C ++这样的语言不会让你难以阻止自己的脚步。

现在,在这种特殊情况下,你可能不会使用this引用范围之外的lambda,但编译器不知道,如果它允许你创建lambda它无法确定是否以可能导致它超过this的方式公开它,因此防止这个问题的唯一方法是禁止某些实际上没有问题的情况。