2013-03-08 77 views
6

在一些C#代碼中看到了一些奇怪的行爲,我不知所措。可能是我錯過了一個重要的理解,所以希望有人可以爲我開燈。C#未能在IEnumerable中設置屬性

得到的代碼塊看起來像這樣:

IEnumberable<myObject> objects = GetObjectsFromApiCall(); 

    for (int i = 0; i < objects.Count(); i++) 
     { 
      if (String.IsNullOrEmpty(objects.ElementAt(i).SubObject.Title)) 
      { 
       SubObject sub = GetSubObjectFromDatabase((long)objects.ElementAt(i).SubObject.Id); 
       if (sub != null) 
       { 
        objects.ElementAt(i).SubObject.Title = sub.Title; 
       } 
      } 
     } 

當你通過它一步,這段代碼的一切似乎正常工作。 「對象」集合按預期填充。 「sub」被提取爲已收集並具有一整套預期屬性,包括已填充的Title屬性。在執行過程中不會引發錯誤。

...但是... SubObject.Title屬性(它只是有標準的get;設定;代碼)存在於每個對象固執地保持爲空。

我不知所措。任何人都可以解釋發生了什麼?

編輯:對於那些建議我不應該使用for循環和ElementAt,我開始使用foreach循環,但認爲它可能是問題的來源,因爲它每次都獲取新的SubObject。現在修復,感謝您的幫助,並恢復了ForEach。

乾杯, 馬特

+4

你去了哪裏:[更新IEnumerable中的item屬性,但屬性不保留設置?](http://stackoverflow.com/a/9104212/93732) – 2013-03-08 12:58:24

+0

此代碼有可能會非常慢甚至不好笑。 – ChaosPandion 2013-03-08 12:58:46

+0

你可以複製/粘貼你的* actual *代碼,而不是* *看起來像* actual *代碼的東西嗎? – ken2k 2013-03-08 13:00:00

回答

4

我會解決這個問題是這樣的:

var objects = GetObjectsFromApiCall().ToList(); 

然後,你可以保持環路是(它的工作原理),或優化有點用的foreach和一些LINQ的其他答案的建議,但它確實並不重要:問題在於您試圖更改IEnumerator上的元素,如@AhmetKakıcı指出的this question中所述。

+1

-1這實際上是錯誤的。修改IEnumerable返回的元素沒有問題。實際上,當你在'ToList()'後面使用'foreach'時,你正在使用'IEnumerable',因爲'List '實現'IEnumerable '。一個問題可能是數據庫查詢的*延遲執行*,但這絕對不是因爲IEnumerable接口的簡單存在...... – ken2k 2013-03-08 15:13:17

+0

你說得對,我讀得太快......問題不在於IEnumerable但它的實施方式。這就是爲什麼使用ToList()有意義。感謝您的澄清。 – Larry 2013-03-08 16:01:47

+0

@ ken2k正確。查看我的答案,瞭解如何修改「IEnumerable」返回的項目。 – 2013-03-08 16:13:39

1

首先,你不應該使用ElementAt()對於這種代碼,使用

foreach (var o in objects) 
{ 
    if (string.IsNullOrEmpty(o.SubObject.Title)) 
    { 
     o.SubObject.Title = ...; 
    } 
} 

另外,應注意的是,如果你的方法返回一個動態IEnumerable那麼每次您致電objects.Something()再次調用API並檢索新鮮副本。如果是這種情況,則應使用.ToList()方法將枚舉複製到列表中。

還有的不把副本列表中的一種方式 - 通過創建一個動態枚舉是這樣的:

objects = objects.Select(o => 
{ 
    if (string.IsNullOrEmpty(o.SubObject.Title)) 
    { 
     o.SubObject.Title = ...; 
    } 
    return o; 
}); 

至於沒有被正確設置(如果以前的事情沒有幫助)的值 - 嘗試在設置器中添加throw new Exception(value)以獲取Title屬性 - 查看是否正在使用正確的值調用該屬性。

+0

「首先,您不應該使用ElementAt()代替這種類型的代碼。」爲什麼? – 2013-03-08 13:43:27

+0

.NET會每次使用'Enumerator.MoveNext()'枚舉'i'次來獲取值。它比'list [i]'方法慢。 – 2013-03-08 13:55:19

2

試試這個

List<myObject> objects = GetObjectsFromApiCall().ToList(); 

foreach(var obj in objects.Where(o => string.IsNullOrEmpty(objects.SubObject.Title)).ToList()) 
{ 
    var subObject = GetSubObjectFromDatabase(obj.SubObject.Id); 
    if(subObject == null) continue; 

    obj.SubObject.Title = subObject.Title; 
} 
1

我來賓功能GetObjectsFromApiCall看起來像以下:

public IEnumberable<myObject> GetObjectsFromApiCall(){ 
    for(var i = 0; i < 10; i++) 
    { 
     yield return new myObject(); 
    } 
} 

如果我是正確的,每次打電話objects.ElementAt(I)函數來獲取對象,您將通過「yield return new myObject()」獲得一個新對象。

+0

這是一個很好的理論。我正在考慮發佈一個這樣的例子。 – 2013-03-08 13:37:18

+0

哦,你應該改變「objects.ElementAt(i).SubObject.Title = sub.Title;」到「var obj = objects.ElementAt(i).SubObject; obj.Title = sub.Title;」 – fengyj 2013-03-08 13:40:12

+0

關於你的評論:它會改變什麼? – 2013-03-08 15:08:52

1

但是,如何檢查Title屬性是否發生了變化?你再撥打GetObjectsFromApiCall()嗎?還是你foreach再次通過相同的objects實例?

IEnumerable實例可能會在每次「枚舉」時創建併產生新對象。所以這裏有一個簡單的插圖示例。對於這個示例,定義:

class SomeObject 
{ 
    public string Title { get; set; } 
} 

然後,我們將考慮兩種類型的「源極」,第一陣列,然後這樣定義的迭代器塊:

static IEnumerable<SomeObject> GetSomeSequence() 
    { 
     yield return new SomeObject { Title = "Alpha", }; 
     yield return new SomeObject { Title = "Beta", }; 
     yield return new SomeObject { Title = "Gamma", }; 
    } 

然後這種方式測試:

static void Main() 
    { 
     IEnumerable<SomeObject> thingsToModify; 

     // set source to an array 
     thingsToModify = new[] { new SomeObject { Title = "Alpha", }, new SomeObject { Title = "Beta", }, new SomeObject { Title = "Gamma", }, }; 

     foreach (var t in thingsToModify) 
      Console.WriteLine(t.Title); 

     foreach (var t in thingsToModify) 
      t.Title = "Changed!"; 

     foreach (var t in thingsToModify) 
      Console.WriteLine(t.Title); // OK, modified 


     // set source to something which yields new object each time a new GetEnumerator() call is made 
     thingsToModify = GetSomeSequence(); 

     foreach (var t in thingsToModify) 
      Console.WriteLine(t.Title); 

     foreach (var t in thingsToModify) 
      t.Title = "Changed!";   // no-one keeps these modified objects 

     foreach (var t in thingsToModify) 
      Console.WriteLine(t.Title); // new objects, titles not modified 

    } 

結論:完全可以修改屬於我們正在迭代的源的可變對象的狀態。但某些類型的IEnumerable源在每次調用時都會產生新的數據副本,然後修改副本無用。