2009-06-24 104 views
2

我碰到一個有趣的問題今天在這裏我有兩個方法,快速瀏覽,都做同樣的事情來了。這是返回一個IEnumerable Foo對象。爲什麼LINQ會以不同的方式處理兩個「相同」事物的方法?

我已經低於它們定義爲列表1和列表2:

public class Foo 
{ 
    public int ID { get; set; } 
    public bool Enabled { get; set;}  
} 

public static class Data 
{ 
    public static IEnumerable<Foo> List1 
    { 
     get 
     { 
      return new List<Foo> 
      { 
       new Foo {ID = 1, Enabled = true}, 
       new Foo {ID = 2, Enabled = true}, 
       new Foo {ID = 3, Enabled = true} 
      }; 
     } 
    } 

    public static IEnumerable<Foo> List2 
    { 
     get 
     { 
      yield return new Foo {ID = 1, Enabled = true}; 
      yield return new Foo {ID = 2, Enabled = true}; 
      yield return new Foo {ID = 3, Enabled = true}; 
     } 
    } 
} 

現在考慮下面的測試:

IEnumerable<Foo> listOne = Data.List1; 
listOne.Where(item => item.ID.Equals(2)).First().Enabled = false; 
Assert.AreEqual(false, listOne.ElementAt(1).Enabled); 
Assert.AreEqual(false, listOne.ToList()[1].Enabled); 

IEnumerable<Foo> listTwo = Data.List2; 
listTwo.Where(item => item.ID.Equals(2)).First().Enabled = false; 
Assert.AreEqual(false, listTwo.ElementAt(1).Enabled); 
Assert.AreEqual(false, listTwo.ToList()[1].Enabled); 

這兩種方法似乎做「相同」的事情。

爲什麼在測試代碼中的第二個斷言失敗?
爲什麼listTwo的第二個「富」項目沒有得到設置爲false時,它是那麼listOne?

注意:我解釋了爲什麼允許這種情況發生,以及兩者的區別。不知道如何解決第二個斷言,因爲我知道如果我添加一個ToList調用List2它將工作。

+0

感謝您的所有答案。我很清楚發生了什麼。我經過一些不同的解釋來清除它!乾杯! – Michael 2009-06-25 22:47:51

回答

6

第一個代碼塊構建一次項目並返回一個包含項目的列表。

每當IEnumerable被瀏覽時,第二塊代碼就會生成這些項目。

這意味着第一個塊的第二行和第三行對同一個對象實例進行操作。第二塊的第二和第三行中的Foo 不同情況下工作(如您遍歷新實例被創建)。

看到這個的最好方法是在方法中設置斷點並在調試器下運行此代碼。第一個版本只會觸發一次斷點。第二個版本將在兩次,一次在.Where()調用期間以及在.ElementAt調用期間一次。 (編輯:使用修改後的代碼,它也會在ToList()調用期間第三次到達斷點)。

這裏要記住的是,迭代器方法(即使用yield return)將會每運行枚舉器迭代通過,而不僅僅是當初始返回值被構造時。

1

listTwo是一個迭代器 - 狀態機。

的ElementAt必須開始在迭代的開始正確地得到了IEnumerable第i個指標(無論它是否是一個迭代器狀態機或一個真正的IEnumerable實例),正因爲如此,listTwo將被重新初始化所有三個項目的默認值Enabled = true。

0

建議:編譯代碼,並與反射器打開。收益率是一種語法結果。您將能夠在您編寫的代碼和爲yield關鍵字生成的代碼中看到代碼邏輯差異。兩者都不一樣。

2

那些絕對是不是一樣的東西。

第一次構建並在您調用它時返回列表,如果需要,您可以將它重新列表並列出它,包括添加或刪除項目,並且一旦將結果在一個變量中,你正在處理那一組結果。調用該函數會產生另一組結果,但重複使用單個調用的結果會影響相同的對象。

第二個構建IEnumerable。您可以枚舉它,但是如果不先調用.ToList()就不能將其視爲列表。實際上,調用該方法不會執行什麼,直到您實際遍歷它爲止。試想一下:

var fooList = Data.List2().Where(f => f.ID > 1); 
// NO foo objects have been created yet. 
foreach (var foo in fooList) 
{ 
    // a new Foo object is created, but NOT until it's actually used here 
    Console.WriteLine(foo.Enabled.ToString()); 
} 

注意,上面的代碼將創建第一個(未使用)的Foo實例,但直到進入foreach循環。所以這些項目直到需要時纔會創建。但這意味着每次你打電話給他們時,你都在建立一套新的物品。

相關問題