2010-09-22 34 views
3

我讀的書曼寧有關LINQ,有一個例子:LINQ表達式中的值是否通過引用傳遞?

static class QueryReuse 
    { 
     static double Square(double n) 
     { 
     Console.WriteLine("Computing Square("+n+")..."); 
     return Math.Pow(n, 2); 
     } 
     public static void Main() 
     { 
     int[] numbers = {1, 2, 3}; 
     var query = 
        from n in numbers 
        select Square(n); 

     foreach (var n in query) 
       Console.WriteLine(n); 

     for (int i = 0; i < numbers.Length; i++) 
       numbers[i] = numbers[i]+10; 

     Console.WriteLine("- Collection updated -"); 

     foreach (var n in query) 
      Console.WriteLine(n); 
    } 
} 

與下面的輸出:

Computing Square(1)... 
1 
Computing Square(2)... 
4 
Computing Square(3)... 
9 
- Collection updated - 
Computing Square(11)... 
121 
Computing Square(12)... 
144 
Computing Square(13)... 
169 

這是否意味着,「數字」是按引用傳遞?這種行爲是否需要執行懶惰的執行和收益?或者我在這裏錯了嗎?

回答

7

這是關於懶惰執行。每次迭代查詢時,都會再次看到numbers。事實上,如果您更改numbers的後期元素的值,而您正在執行查詢,那麼您也會看到該更改。這全部改變了陣列的內容

請注意,查詢在創建查詢時會記住numbers的值 - 但該值是數組的參考,而不是內容。所以,如果你改變numbers本身的價值是這樣的:

numbers = new int[] { 10, 9, 8, 7 }; 

那麼變化不會反映在查詢中。

只是,如果你查詢的其他部分中使用的變量,像這樣的事情複雜化,:

int x = 3; 

var query = from n in numbers 
      where n == x 
      select Square(n); 

那麼變量x被捕獲,而不是它的價值...所以改變x將改變評估查詢的結果。這是因爲查詢表達式真的翻譯成:在這裏,x是lambda表達式中使用

var query = numbers.Where(n => n == x) 
        .Select(n => Square(n)); 

注意,但numbers不是 - 這就是爲什麼他們的表現略有不同。

-1

是的,numbers變量是通過引用傳遞的,不是因爲您使用LINQ,而是因爲數組是引用類型。

輸出變化的事實是由於LINQ的推遲/懶惰評估。

+1

「按引用傳遞」和「按值傳遞引用」有所不同。請參閱http://pobox.com/~skeet/csharp/parameters.html – 2010-09-22 09:16:47

6

numbers的引用通過。但是,查詢是懶惰評估的,而底層數組是可變的。

那麼這是什麼意思?

var arr = new[]{1,2,3,}; 
var q = arr.Select(i=>i*2); 
Console.WriteLine(string.Join(", ",q.ToArray())); //prints 2, 4, 6 
arr[0]=-1; 
Console.WriteLine(string.Join(", ",q.ToArray())); //prints -2, 4, 6 
// q refers to the original array, but that array has changed. 
arr = new[]{2,3,4}; 
Console.WriteLine(string.Join(", ",q.ToArray())); //prints -2, 4, 6 
//since q still refers to the original array, not the variable arr! 

一般情況下,它可以得到,如果你改變的變量,而不是他們的基礎對象混淆很快,所以最好避免這樣的變化。

例如:

var arr = new[]{1,2,}; 
var arr2 = new[]{1,2,}; 
var q = from a in arr 
     from b in arr2 
     select a*b; 

// q is 1,2,2,4 
arr = new[]{0,1}; //irrelevant, arr's reference was passed by value 
// q is still 1,2,2,4 

arr2 = new[]{0,1}; //unfortunately, relevant 
// q is now 0, 1, 0, 2 

要理解這一點,你需要了解編譯過程的細節。查詢表達式被定義爲等效於嚴重使用閉包的擴展方法語法(arr.Select...)。因此,實際上只有第一個可枚舉或可查詢的值通過值傳遞,其餘的都是在閉包中捕獲的,這意味着它們的引用通過引用有效地傳遞。困惑了嗎? 避免更改像這樣的變量,以保持代碼的可維護性和可讀性。

3

查詢的存儲方式完全相同 - 不是結果集,只是查詢。

當您從查詢中請求結果時,它會使用執行查詢時的當前值而不是查詢創建時的值來評估查詢。如果您對相同的查詢進行了兩次評估,那麼如果基礎數據已更改,您可以得到不同的結果,如您在問題中提供的示例所示。

0

這是因爲對numbers的引用是closure,並且結合懶惰執行的枚舉給出了這個結果。

相關問題