2010-04-13 63 views
2

声明具有相同名称的那些变量,我有以下代码(生成给出的A,B的二次函数,以及c)
Func<double, double, double, Func<double, double>> funcGenerator = (a, b, c) => f => f * f * a + b * f + c;
截至目前为止,可爱。然后,如果我尝试声明一个名为a,b,c或f的变量,visual studio会弹出一个"A local variable named 'f' could not be declared at this scope because it would give a different meaning to 'f' which is used in a child scope."
基本上,这会失败,我不知道为什么,因为子范围甚至不会生成任何感。C#要疯了,当我在一个lambda

Func<double, double, double, Func<double, double>> funcGenerator = 
    (a, b, c) => f => f * f * a + b * f + c; 
var f = 3; // Fails 
var d = 3; // Fine 

这是怎么回事?

回答

12

我认为你误解的是,声明的顺序与C#编译器在范围规则方面无关。

此:

Func<double, double, double, Func<double, double>> funcGenerator = 
    (a, b, c) => f => f * f * a + b * f + c; 
var f = 3; 
var d = 3; 

是完全一样的:

var f = 3; 
Func<double, double, double, Func<double, double>> funcGenerator = 
    (a, b, c) => f => f * f * a + b * f + c; 
var d = 3; 

作用域不是顺序敏感。您有一个名为f的本地变量,并且您正试图在lambda内声明另一个名为f的变量。根据C#规范,这是非法的。

具体而言,它会与lambda进行变量捕获的能力相冲突。例如,下面的代码是合法的:

int x = 3; 
Func<int> func =() => x + 1; 

这是完全合法和执行func()将返回4。这就是为什么你不能在lambda中声明另一个变量x - 因为lambda实际上需要能够捕获外部x

只需更改f变量之一的名称即可。

8

这意味着它说什么。您不允许在lambda范围和包含lambda的范围中使用相同的变量名称。子范围是lambda的范围。

+0

这是如何产生任何意义?没有儿童范围。子范围意味着另一组{},其中引入了一个不同的变量,但这不会发生在这里。函数定义已经结束。 – Rubys 2010-04-13 14:25:32

+0

如果在外部范围引入f,它在lambda中可用,它给f赋予不同的含义。这个不清楚的是什么? – flq 2010-04-13 14:29:25

+3

Rubys,有*是*的范围。范围在C#中不需要'{}'。无关紧要的是'var f'上的文本上的lambda定义。 – 2010-04-13 14:30:01

0

变量在函数的范围内声明。由于lambda被编译/重写为同一函数内的某些代码,因此它具有某种意义。我认为for循环是唯一的例外,您将变量定义为for循环的“第一个参数”。

虽然我可以想象如果您可以重复使用变量名,它会很方便。

+2

如果你说的“方便”,你的意思是“一个难以发现错误的无穷无尽的源泉”,那么我同意。 – Kevin 2010-04-13 14:31:56

2

来自封闭范围的lambda参数和变量位于相同的“名称空间”(松散名称绑定意义上,而不是语言特征意义上)的原因是因为lambda可以关闭(引用)变量封闭的范围。拉姆达的参数必须与变量区分的拉姆达可以看到:

int x; 
Action a =() => { x = 3; }; 

这没关系,它分配3到外x

int x; 
Action<int> a = x => { x = 3; }; 

这是不好的 - 我们分配给哪3个x

在你的例子中唯一的区别是声明的顺序。这将产生一个错误

Action a =() => { x = 3; }; 
int x = 2; 

编译器会说这是声明之前,你不能引用x。如果你那么做的拉姆达带一个参数具有相同的名称,我们得出大致的例子:

Action<int> a = x => { x = 3; }; 
int x; 

是否奏效,编译器会基本上可以如下形式的规则:“尝试各种可能的解释的代码,并且无论哪一个不是错误的,都假定它是预期的意思“。 C#采取了一种更安全的方法,并期望您具体而明确,而不是依靠这样的规则来“挑选赢家”。

7

确定究竟违反了哪个C#规则可能非常困难。作为一个公共服务我写的介绍了一些更容易混淆的作用域规则之间的差异这个方便的指南:(开始在底部,这些都是逆时间顺序排列)

http://ericlippert.com/tag/simple-names/

此外,您对“范围”的概念似乎有点不清楚 - 这并不令人意外,因为在大多数书中,这个词几乎意味着作者想要的任何东西。在C#中,我们仔细地将“范围”定义为“程序文本的区域,其中可以通过其非限定名称引用特定实体”。因此,例如,在

namespace A 
{ 
    public class B 
    { 
     private int c; 
     protected int d; 
     public void E(int f) 
     { 
     int g = f; 
     Func<int, int> h = i => g * i; 
     } 
    } 
    public class K : B { } 
} 

A的范围无处不在。 B和K的范围在A的声明中随处可见。c的范围在B中无处不在。d和E的范围是B,K的内容以及从B或K派生的任何类的内容。f和g和h是E的主体。我的范围是lambda的主体。

请注意,范围和可访问性域之间存在差异。 B,K和E在任何地方都可以访问,但在特定位置范围内只有

还要注意h在范围内整个该块。 使用h在声明之前是合法的,但在声明之前的范围是

+0

@BenVoigt:你说得对,我输入了那个错误。我会解决它。谢谢! – 2015-04-20 17:56:36

+0

@BenVoigt:澄清你的评论:在C#中,g和h的范围遍及包含它们的整个本地声明空间。C#没有C++的规则,其中一个变量名仅在其声明后在词法上处于范围之内。在声明前使用地方是非法的,但地方在声明之前已在范围之内;一个微妙的区别! – 2015-04-20 17:59:58