Dedikleri gibi, şeytan ayrıntılarda ...
İki toplama numaralandırma yöntemi arasındaki en büyük fark foreach
, devleti taşıması, oysa ForEach(x => { })
bunu taşımamasıdır.
Ancak biraz daha derinlemesine inceleyelim, çünkü kararınızı etkileyebilecek bazı şeyler var ve her iki durum için kod yazarken dikkat etmeniz gereken bazı uyarılar var.
List<T>
Küçük deneyimizde davranışı gözlemlemek için kullanalım . Bu deneme için .NET 4.7.2 kullanıyorum:
var names = new List<string>
{
"Henry",
"Shirley",
"Ann",
"Peter",
"Nancy"
};
foreach
İlk önce bunu tekrarlayalım :
foreach (var name in names)
{
Console.WriteLine(name);
}
Bunu şu şekilde genişletebiliriz:
using (var enumerator = names.GetEnumerator())
{
}
Numaralandırıcı eldeyken, örtülerin altına baktığımızda:
public List<T>.Enumerator GetEnumerator()
{
return new List<T>.Enumerator(this);
}
internal Enumerator(List<T> list)
{
this.list = list;
this.index = 0;
this.version = list._version;
this.current = default (T);
}
public bool MoveNext()
{
List<T> list = this.list;
if (this.version != list._version || (uint) this.index >= (uint) list._size)
return this.MoveNextRare();
this.current = list._items[this.index];
++this.index;
return true;
}
object IEnumerator.Current
{
{
if (this.index == 0 || this.index == this.list._size + 1)
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
return (object) this.Current;
}
}
İki şey hemen ortaya çıkıyor:
- Altta yatan koleksiyon hakkında derinlemesine bilgi sahibi olan durumsal bir nesne döndürülür.
- Koleksiyonun kopyası sığ bir kopyadır.
Bu elbette hiçbir şekilde güvenli değildir. Yukarıda belirtildiği gibi, yineleme sırasında koleksiyonun değiştirilmesi sadece kötü mojo'dur.
Peki, koleksiyonun yineleme sırasında koleksiyonun dışına çıkarak yineleme sırasında geçersiz hale gelmesi sorunu ne olacak? En iyi uygulamalar, koleksiyonun işlemler ve yineleme sırasında sürümlendirilmesini ve temel alınan koleksiyonun ne zaman değiştiğini algılamak için sürümleri kontrol etmenizi önerir.
Burada işler gerçekten bulanıklaşıyor. Microsoft belgelerine göre:
Koleksiyonda öğe ekleme, değiştirme veya silme gibi değişiklikler yapılırsa, numaralandırıcının davranışı tanımlanmamıştır.
Peki, bu ne anlama geliyor? Örnek vermek gerekirse, List<T>
istisna işleme uygulamalarının uygulanması IList<T>
, uygulayan tüm koleksiyonların aynı şeyi yapacağı anlamına gelmez . Bu Liskov İkame İlkesinin açık bir ihlali gibi görünüyor:
Bir üst sınıfın nesneleri, uygulamayı bozmadan alt sınıflarının nesneleri ile değiştirilebilir.
Başka bir sorun, numaralandırıcının uygulaması gerektiğidir IDisposable
- bu, yalnızca arayan yanlış yaparsa değil, yazar Dispose
deseni doğru bir şekilde uygulamıyorsa, başka bir potansiyel bellek sızıntısı kaynağı anlamına gelir .
Son olarak, bir ömür boyu sorunumuz var ... yineleyici geçerliyse, ancak temel alınan koleksiyon gittiğinde ne olur? Şimdi ne olduğunun fotoğrafı ... bir koleksiyonun ömrünü ve yineleyicilerini ayırdığınızda sorun istiyorsunuz.
Şimdi inceleyelim ForEach(x => { })
:
names.ForEach(name =>
{
});
Bu, aşağıdakilere genişler:
public void ForEach(Action<T> action)
{
if (action == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
int version = this._version;
for (int index = 0; index < this._size && (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5); ++index)
action(this._items[index]);
if (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
return;
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
Önemli not aşağıdakilerdir:
for (int index = 0; index < this._size && ... ; ++index)
action(this._items[index]);
Bu kod hiçbir numaralandırıcı (hiçbir şey Dispose
) ayırmaz ve yineleme sırasında duraklamaz .
Bunun, temel alınan koleksiyonun sığ bir kopyasını da gerçekleştirdiğini, ancak koleksiyonun artık bir anlık görüntü olduğunu unutmayın. Yazar, koleksiyonun değiştirilmesi veya eski hale gelmesi için doğru bir denetim uygulamazsa, anlık görüntü hala geçerlidir.
Bu sizi hiçbir şekilde yaşam boyu sorunlardan korumaz ... temeldeki koleksiyon kaybolursa, şimdi ne olduğunu gösteren sığ bir kopyanız var ... ama en azından Dispose
probleminiz yok yetimsiz yineleyicilerle uğraşmak ...
Evet, yineleyiciler dedim ... bazen devlete sahip olmak avantajlıdır. Bir veritabanı imlecine benzer bir şey korumak istediğinizi varsayalım ... belki birden fazla foreach
stil Iterator<T>
'nin yolu budur. Kişisel olarak bu tasarım tarzından hoşlanmıyorum, çünkü çok fazla yaşam boyu sorun var ve güvendiğiniz koleksiyonların yazarlarının iyi ifadelerine güveniyorsunuz (kelimenin tam anlamıyla kendiniz sıfırdan yazmazsanız).
Her zaman üçüncü bir seçenek vardır ...
for (var i = 0; i < names.Count; i++)
{
Console.WriteLine(names[i]);
}
Seksi değil, ama dişleri var ( Tom Cruise ve The Firm filmi )
Seçim senin, ama şimdi biliyorsun ve bilgili biri olabilir.