2012-01-26 12 views
6

olarak adlandırılır. Şu anda bir akıştan öğe koleksiyonu okuyorum.verim her zaman

public class Parser{ 

private TextReader _reader; //Get set in Constructor 
private IEnumerable<Item> _items;  

public IEnumerable<Item> Items{ 
    get{ 
    //I >>thought<< this would prevent LoadItems() from being called twice. 
    return _items ?? (_items = LoadItems()); 
    } 
} 

public IEnumerable<Item> LoadItems(){ 
    while(_reader.Peek() >= 0){ 
    yield return new Item(_reader.ReadLine()); //Actually it's a little different 
    } 
} 
} 

Let İki öğeleri içeren bir akışı var ki, ben aşağıdakileri yapın: Ben şöyle bunu result2 biri iken

var textReader = //Load textreader here 
var parser = new Parser(textReader); 
var result1 = parser.Items.Count(); 
var result2 = parser.Items.Count(); 

Şimdi result1, 2'dir.

Şimdi fark ettim ki, boş çekmem işe yaramaz mı? Görünüşe göre, her seferinde bu fonksiyonu çağırıyorum.

Birisi bana bunun neden olduğunu açıklayabilir mi? Ve bu durum için en iyi çözüm ne olurdu (lütfen bana ne yaptığımı söyleyeyim tamam mı?). LoadItems (yield kullanır) tembel enumerable ve bir alana atamadan Çünkü

+0

yup, her seferinde vermelidir yüzden onun her zaman boş ve yük öğeleri her zaman denir. –

+0

Lütfen yanılıyorsam beni düzeltin ama ben burada atadım '_items = LoadItems() ' –

cevap

5

, bu _items numaralandırmak her zaman aslında LoadItems() içinde döngü neden oluyor Enumerable.Count yeni oluşturur, yani (yeniden çalıştırılmak üzere demektir Enumerator her seferinde LoadItems gövdesinin tekrar çalışmasına neden olur). Okuyucuyu LoadItems içinde her seferinde yeniden oluşturmadığınız için, imleci akışın sonunda konumlandırılacak ve bu yüzden muhtemelen daha fazla satır okuyamayacaktır - null döndürdüğünden şüpheleniyorum ve tek Item nesnesini döndürdüğünüzden ikinci çağrı null dizgisini içerir. (Mümkünse)

return _items ?? (_items = LoadItems().ToList()); 

Veya akımının başlangıcına geri okuyucuyu arayan: Buna

Çözümler size somut bir listesini verecektir hangi Enumerable.ToList arayarak LoadItems sonucu 'sandığından da' olacaktır LoadItems her seferinde aynı şekilde tekrar çalışabilir.

Ancak bu durumda yield girişinden kurtulmanızı ve kazanç elde etmemek için karmaşıklık bedelini ödemeniz için çok az fayda olduğundan somut bir liste döndürmenizi tavsiye ederim.

+0

Ben de böyle düşündüm. Fakat bir kaynağı okurken ve öğelerin geri dönüşünü sağladığınızda. Kaynakları kaybetmediğinden nasıl emin olabilirsiniz. –

+0

Okuyucunun sonucunu, okunan satırları 'IEnumerable'ında' Enumerable.ToList 'yi arayarak veya bir' List <> '(veya başka bir veri yapısını) el ile oluşturarak, somut bir liste halinde gerçekleştirerek koruyabilirsiniz. Örneğin 'LoadItems() .List()' veya 'yeni liste (LoadItems())'. –

+0

Aah teşekkürler! Verim hatası fikrini tamamen yanlış anladım! Açıkladığın için teşekkürler :) –

1

Değişken adınız sizi yoldan çıkardı. Şu anda: muhtemelen yükleme tembel olmak istiyorum ve öğeleri tasarrufu (değişken adı olarak önerir) oysa

private IEnumerable<Item> _items; 

Eğer yükleme tembel ve yineleyici tasarruf şunlardır:

public class Parser{ 

private TextReader _reader; //Get set in Constructor 
private List<Item> _items;  

public IEnumerable<Item> Items{ 
    get{ 
    return _items ?? (_items = LoadItems().ToList()); 
    } 
} 

private IEnumerable<Item> LoadItems(){ 
    while(_reader.Peek() >= 0){ 
    yield return new Item(_reader.ReadLine()); //Actually it's a little different 
    } 
} 
} 
1

yield'un kısa el olarak kullanıldığını düşünün. Kodunuz gibi bir şey dönüştü alır:

private class <>ImpossibleNameSoItWontCollide : IEnumerator<Item> 
{ 
    private TextReader _rdr; 
    /* other state-holding fields */ 
    public <>ImpossibleNameSoItWontCollide(TextReader rdr) 
    { 
    _rdr = rdr; 
    } 
    /* Implement MoveNext, Current here */ 
} 
private class <>ImpossibleNameSoItWontCollide2 : IEnumerable<Item> 
{ 
    private TextReader _rdr; 
    /* other state-holding fields */ 
    public <>ImpossibleNameSoItWontCollide2(TextReader rdr) 
    { 
    _rdr = rdr; 
    } 
    public <>ImpossibleNameSoItWontCollide GetEnumerator() 
    { 
    return new <>ImpossibleNameSoItWontCollide(_rdr); 
    } 
    /* etc */ 
} 
public IEnumerable<Item> LoadItems() 
{ 
    return new <>ImpossibleNameSoItWontCollide2(_rdr); 
} 

Dolayısıyla LoadItems() aslında sadece bir kez denir, ama döner nesne GetEnumerator() iki kez çağrıda bulunur.

Ve TextReader ilerlediğinden, bu sizin için yanlış sonuçlar verir. Her ne kadar tüm öğeleri tutmaktan daha düşük bir bellek kullanımına yol açtığına dikkat etmesine rağmen, aynı öğeleri iki kez kullanmak istemediğinizde faydaları olur.Eğer istiyorsun yana

, bunları depolayan bir nesne oluşturmak gerekir: her yerde '_items' değişken atama olmadığı için

return _items = _items ?? _items = LoadItems().ToList(); 
+0

Ah harika, bu cevabı gerçekten çok beğendim çünkü verimdeki sözdizimsel şekerin aslında ne yaptığını açıkladın! –

+0

Rica ederim. 'TextReader' 'bittikten sonra' TextReader 'imha etmek için' using 'bloğunu bir' using 'bloğu koyarsanız, sayımın' Dispose() 'yöntemini kullanacağını (ancak numaralandırmayı değil; Yani numaralandırma aslında numaralandırılmadıysa, o zaman çağrılmayacaktır). –