2015-12-14 50 views
9

Nesnelerin birçoğunu oluşturan ve her bir nesnenin Priority özelliğini ayarlamak için bunların üzerinden yineleyen gerçekten basit bir programım var.IEnumerable - foreach döngüsünün içindeki nesneleri güncelle

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; } 
} 

Değiştirilmiş önceliklerle nesneleri içerecek Ana yöntemde IEnumerable bekliyorum.

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 davranışın nedeni nedir ve ben çalışıyorum benim öncelikleri almak için buraya neyi değiştirmelisiniz: Ancak, hepsi varsayılan değeri 0. Burada günlük olduğunu var?

+1

iki önemli gerçekleri: (1) bir sorgu ifadesinin değeri * Sorgu *, ve (2) bir sorgu yürütüldüğünde * sorgu yürütüldüğünde *. Bu ifade tatolojiler gibi görünebilir, ancak kodunuz onlara inanmadığınızı gösterir; Bir sorgu değerinin, sorgunun yürütülmesi olduğuna ve iki kez yürütmenin yalnızca bir kez gerçekleştirdiğine inandınız. –

+0

@EricLippert, haklısınız, her zaman IEnumerable'ı bir sorgu temsili olarak değil (adından da anlaşılacağı gibi) _enumerate_ öğesi olarak tanımlayabilirim. Aynı zamanda, sadece diziden geçmeye ihtiyaç duyduğumda (ve asla eleman eklememeyi/çıkarmam), ToList'i aramadan tüm programım boyunca IEnumerable'ı güvenle kullanabileceğimi anlamış görünüyordu. Birçok kitap/makale önerdiği gibi, "yöntemler/sınıflar/programlar arasında geçiş yapmak için" mümkün olan en genel türü kullanmalısınız "ve burada yapmaya çalıştığım şey buydu. Şimdi görüyorum ki bundan daha karmaşık. –

cevap

15

Bunu yaptığınızda: Sadece bir lazily değerlendirilen yineleyici oluştururken

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

, bu ObjectWithPriorty size proje depolamak için bir dizi/liste tahsis etmez. Yineleyiciyi numaralandırdığınız her seferde, değerleri yinelemek ve her yineleme için bir ObjectWithPriority yansıtmak, ancak bunları atar.

Yapmak istediğiniz şey, iletmeden önce sorguyu yansıtmaktır, bu nedenle daha sonra önceden ayrılmış liste/diziyi gerçekten değiştireceksiniz. Bu Enumerable.ToList veya Enumerable.ToArray kullanılarak elde edilebilir:

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

Yapabilirsin ek kullanım Enumerable.Range yerine istendiği gibi tembel numaralarını proje olacak Sabit boyutlu bir dizi, tahsis:

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

Açıklama için teşekkürler! Bu tüm IEnumerable konsepti biraz kafa karıştırıcı. Bu nedenle, bazı dış verileri (yani bir dosya veya DB tablosu) okuyan ve onu etki alanı model nesnelerine dönüştüren bir yöntemim varsa, bu tür sorunları önlemek için dönüş türünü "IEnumerable" yerine "List" olarak değiştirebilir miyim? Diğer kod bu yöntemin geri döndürdüğü verileri manipüle eder? –

+0

Bir veritabanını sorgularken, arayan kişiye geri döneceğiniz herhangi bir IEnumerable 'ile çok dikkatli olmalısınız. Örneğin, bir numarayı arayan kişiye verirseniz ve LINQ veya bir "foreach" ifadesi ile birden çok kez numaralandırırsanız, sorgu birden çok kez çalıştırılır. Belki de, bir "T []" veya bir "IList " döndürmekten daha iyidir. –

0

Yanıt Yanı Sıra Yuval Itzchakov ait:

: Yapmak istediğiniz

yapabildin senin nesnelerin öncelik tembel-yük

sadece bir nesne için ApplyPriorities() Yöntem tanımlayın basıp-Yöntem kullanmak VEYA ObjectWithPriority sınıf wich için bir temsilci eklemek için aşağıdaki kodu gösterildiği gibi Öncelik hesaplar:

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

daha iyi neler olduğunu anlamak için programda, bir sorgusu olarak bu ifadenin

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

düşünmek gerekir, değil nesneleri dizisi/listeye/koleksiyonu olarak.

Ancak bu programda bir sorguya ihtiyacınız olmadığını kodunuzdan açıkça göreceksiniz. Sonlu sayıda nesneye sahip bir koleksiyona ihtiyacınız var ve foreach'u kullanarak her geçişinizde aynı nesneleri döndürür.

Yapılması gereken en iyi şey, IEnumerable<ObjectWithPriority> yerine ICollection<ObjectWithPriority> kullanmak olacaktır. Bu, programın mantığını daha iyi temsil eder ve sizin tökezlediğiniz gibi bazı yanlış/yanlış yorumlamalardan kaçınmanıza yardımcı olur.

aşağıdaki gibi kod modifiye edilebilir: Eğer LINQ hakkında içselleştirmelidir

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)); 
    } 
}