2015-12-14 73 views
9

我有一個非常簡單的程序,它可以創建一堆對象並遍歷它們來設置每個對象的Priority屬性。IEnumerable - 更新foreach循環內的對象

static void Main(string[] args) 
{ 
    foreach (var obj in ObjectCreator.CreateObjectsWithPriorities()) 
     Console.WriteLine(String.Format("Object #{0} has priority {1}", 
             obj.Id, obj.Priority)); 
} 

class ObjectCreator 
{ 
    public static IEnumerable<ObjectWithPriority> CreateObjectsWithPriorities() 
    { 
     var objs = new[] { 1, 2, 3 }.Select(i => new ObjectWithPriority() { Id = i }); 
     ApplyPriorities(objs); 
     return objs; 
    } 

    static void ApplyPriorities(IEnumerable<ObjectWithPriority> objs) 
    { 
     foreach (var obj in objs) 
     { 
      obj.Priority = obj.Id * 10; 
      Console.WriteLine(String.Format("Set priority of object #{0} to {1}", obj.Id, obj.Priority)); 
     } 
    } 
} 

class ObjectWithPriority 
{ 
    public int Id { get; set; } 
    public int Priority { get; set; } 
} 

我在遏制與修改優先對象主要方法期待IEnumerable。然而,所有的人都默認值爲0 這裏是日誌:

Set priority of object #1 to 10 
Set priority of object #2 to 20 
Set priority of object #3 to 30 
Object #1 has priority 0 
Object #2 has priority 0 
Object #3 has priority 0 

什麼是suche行爲的原因,我應該爲了得到我的優先工作改變嗎?

+1

兩個重要的事實:(1)查詢表達式的值是一個*查詢*,和(2)在執行查詢*查詢執行時*。這些陳述似乎是重言式,但是你的代碼表明你不相信他們;您認爲查詢的值是查詢的執行,並且執行兩次的查詢只執行一次。 –

+0

@EricLippert,你是對的,我一直認爲IEnumerable是一系列我可以通過(如名字所示)枚舉的元素,而不是查詢表示。這似乎也有道理,如果我只需要遍歷序列(並且永遠不添加/刪除元素),我可以安全地在整個程序中使用IEnumerable,而無需調用ToList。正如許多書籍/文章所暗示的那樣,「你應該使用可能的最普通類型」來在方法/類/程序之間傳遞對象,這就是我在這裏要做的。現在我發現它比這更復雜。 –

回答

15

當你這樣做:

var objs = new[] { 1, 2, 3 }.Select(i => new ObjectWithPriority() { Id = i }); 

你只需創建一個懶洋洋地評估迭代,這不分配數組/列表存儲ObjectWithPriorty你的項目。每次枚舉迭代器時,它會再次迭代這些值,併爲每次迭代計劃一次ObjectWithPriority,但會放棄它們。

你想要做的是在傳遞它之前實現查詢,所以後面實際上會修改已經分配的列表/數組。這可以通過使用Enumerable.ToListEnumerable.ToArray來實現:

public static IEnumerable<ObjectWithPriority> CreateObjectsWithPriorities() 
{ 
    var objs = new[] { 1, 2, 3 }.Select(i => new ObjectWithPriority() { Id = i }) 
           .ToList(); 
    ApplyPriorities(objs); 
    return objs; 
} 

,你可以額外使用Enumerable.Range,而不是分配一個固定大小的數組,這將懶洋洋地項目數量的要求:

var objs = Enumerable.Range(1, 3).Select(i => new ObjectWithPriority { Id = i }) 
           .ToList(); 
+0

感謝您的解釋!這整個IEnumerable概念有點混亂。因此,如果我有一個讀取一些外部數據(即文件或數據庫表)的方法並將其轉換爲我的領域模型對象,我是否應該將其返回類型更改爲'List'而不是'IEnumerable'以避免此類問題其他代碼操縱這個方法返回的數據? –

+0

當您查詢數據庫時,您應該非常小心地使用任何'IEnumerable '您返回給調用者。例如,如果你將這樣的枚舉交給一個調用者,並且用LINQ或'foreach'語句多次枚舉它,那麼查詢將被執行多次。也許,你最好返回一個'T'或'IList'。 –

0

除了回答Yuval Itzchakov

如果您想延遲加載對象的優先級,您可以:

定義您ApplyPriorities()方法只是一個對象,然後在選擇法使用或委託添加到您的ObjectWithPriority類至極計算像下面的代碼所示的優先級:

class ObjectWithPriority 
{ 
    public int Id { get; set; } 

    private int? priority; 
    public int Priority { 
     get 
     { 
      return (priority.HasValue ? priority.Value : (priority = PriorityProvider(this)).Value); 

     } 

     set { priority = value; } 
    } 

    Func<ObjectWithPriority, int> PriorityProvider { get; set; } 

    public ObjectWithPriority(Func<ObjectWithPriority, int> priorityProvider = null) 
    { 
     PriorityProvider = priorityProvider ?? (obj => 10 * obj.Id); 
    } 
} 
2

爲了更好地瞭解發生了什麼在你的程序,你應該覺得這表達

var objs = new[] { 1, 2, 3 }.Select(i => new ObjectWithPriority() { Id = i }); 

作爲查詢,而不是作爲一個對象序列/列表/集合。

但是從代碼中可以明顯看出,在這個特定的程序中,你不需要查詢。您需要一個具有有限數量對象的集合,並且每次使用foreach進行循環時都會返回相同的對象。

所以一個體面的事情是使用ICollection<ObjectWithPriority>而不是IEnumerable<ObjectWithPriority>。這樣可以更好地代表程序的邏輯,並有助於避免像偶然發現的錯誤/錯誤解釋。

的代碼可以作如下修改:必須內在約LINQ

public static ICollection<ObjectWithPriority> CreateObjectsWithPriorities() 
{ 
    IEnumerable<ObjectWithPriority> queryThatProducesObjectsWithPriorities = new[] { 1, 2, 3 }.Select(i => new ObjectWithPriority() { Id = i }); // just for clarification 
    ICollection<ObjectWithPriority> objectsWithPriorities = queryThatProducesObjectsWithPriorities.ToList(); 
    ApplyPriorities(objectsWithPriorities); 
    return objectsWithPriorities; 
} 

static void ApplyPriorities(ICollection<ObjectWithPriority> objs) 
{ 
    foreach (var obj in objs) 
    { 
     obj.Priority = obj.Id * 10; 
     Console.WriteLine(String.Format("Set priority of object #{0} to {1}", obj.Id, obj.Priority)); 
    } 
}