2015-07-23 33 views
34

Birisi bu kodun neden sonsuz döngü içinde çalıştığını açıklayabilir mi? Neden MoveNext() her zaman true'u döndürür?Enumerator.MoveNext() 'ın garip davranışı

var x = new { TempList = new List<int> { 1, 3, 6, 9 }.GetEnumerator() }; 
while (x.TempList.MoveNext()) 
{ 
    Console.WriteLine("Hello World"); 
} 

cevap

40

List<T>.GetEnumerator() kesilebilir bir değer türü (List<T>.Enumerator) döndürür. Bu değeri anonim tipte saklıyorsun. değerin kopyası -

Şimdi
while (true) 
{ 
    var tmp = x.TempList; 
    var result = tmp.MoveNext(); 
    if (!result) 
    { 
     break; 
    } 

    // Original loop body 
} 

biz MoveNext() arayarak şeyi not: hiç

while (x.TempList.MoveNext()) 
{ 
    // Ignore this 
} 

eşdeğer: Artık

, bunu ne bir göz atalım anonim tipte olan. Anonim türdeki değeri gerçekten değiştiremezsiniz - sahip olduğunuz her şey, aradığınız değerin bir kopyasını verecek olan bir özelliktir.

Eğer kodu değiştirirseniz:

var x = new { TempList = (IEnumerable<int>) new List<int> { 1, 3, 6, 9 }.GetEnumerator() }; 

... sonra anonim tip bir referansı alma bitireceğiz. Değiştirilebilir değeri içeren bir kutuya bir başvuru. Bu referansta MoveNext() numaralı telefonu aradığınızda, kutu içindeki değer değişecektir, böylece istediğiniz şeyi yapacağız.

Çok benzer bir durumda (List<T>.GetEnumerator() kullanarak) analiz için bkz. my 2010 blog post "Iterate, damn you!".

+3

Buna bakıyorum ve burada hangi öğenin kopyalama/referans ayrımının gerçekten bir fark yarattığı bir değer türü olduğunu bulmakta zorlanıyorum. –

+1

bunu anlamadım, eğer IEnumerable '' a çevirirsek nasıl iyi çalışır? @JonSkeet basit kelimelerle daha fazlasını açıklayabilir –

+1

@EhsanSajjad: 'TempList''in derleme zamanı türü 'IEnumerable ' olduğunda, erişim yalnızca bir referans kopyalar. Bu referansta hareket etmek kutulu değeri değiştirecektir ve bir dahaki sefere bir referansı kopyaladığınızda yine de aynı kutulu değere atıfta bulunacaktır, böylece değişikliği göreceksiniz. Bunu, değer türü değerini kopyalayarak ve bu kopyayı mutasyona sokarak karşılaştır - orijinali kopyaladığınızda * sonraki *, önceki değişikliği görmezsiniz. –

3

vb.NET'te C# foreach yapı ve For Each döngü genellikle IEnumerable<T> uygulamak tipleri ile kullanılsa da, bu dönüş türü uygun bir MoveNext fonksiyonu ve Current özelliği sağlayan bir GetEnumerator yöntemi içeren herhangi bir tür kabul edecektir. GetEnumerator'un bir değer türü döndürmesi,'un IEnumerator<T> döndürdüğü takdirde daha verimli bir şekilde uygulanmasına izin verir. Bir GetEnumerator yöntem çağrısıyla çağrıldığında da birini tedarik etmeden foreach açılmak istenen bir tür bir değer tipi numaralandırıcıyı sağlayabilmektedir hiç bir yol yoktur çünkü

yazık ki, List<T> yazarları, küçük bir performans trade-off karşı karşıya anlambilimine karşı. O zaman, C# değişken tür çıkarımını desteklemediğinden, List<T>.GetEnumerator döndürülen değeri kullanan herhangi bir kod, IEnumerator<T> veya List<T>.Enumerator türünde bir değişken bildirmek zorunda kalacaktır. Eski türünü kullanan kod, List<T>.Enumerator bir başvuru türü gibi davranır ve ikincisini kullanan bir programcının, bir yapı türü olduğunu fark ettiği varsayılabilir. Ancak C# tipi çıkarımda bulunulduğunda, bu varsayım beklemeye son verildi. Kod, bu tipin varlığını bilen programcı olmadan List<T>.Enumerator türünü kullanarak kolayca sonlanabilir.

C#, salt okunur yapılar üzerinde çalıştırılamaz olması gereken yöntemleri etiketlemek için kullanılabilecek bir struct-method niteliğini tanımlayacak olsaydı, ve List<T>.Enumerator bunu kullanmışsa, sizinki gibi kod düzgün bir şekilde verilebilir. Yanlışlık davranışı yerine MoveNext numaralı çağrıya derleme zamanı hatası. Ancak, böyle bir özellik eklemek için belirli bir plan bilmiyorum.