2010-04-13 64 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