İf (öğeler! = Null) foreach'ten önce gereksiz mi (öğelerdeki T öğesi)?


109

Sık sık aşağıdaki gibi kodlarla karşılaşıyorum:

if ( items != null)
{
   foreach(T item in items)
   {
        //...
   }
}

Temel olarak ifkoşul, foreachbloğun yalnızca itemsboş değilse yürütülmesini sağlar . Durumun ifgerçekten gerekli olup olmadığını merak ediyorum , yoksa foreachdavayı halledecek mi items == null?

Demek istediğim, basitçe yazabilir miyim

foreach(T item in items)
{
    //...
}

itemsboş olup olmadığı konusunda endişelenmeden ? Mı ifkoşul gereksiz? Veya buna bağlıdır tip ait itemsbelki veya Tyanı?



1
@ kjbartel cevabı ( "at stackoverflow.com/a/32134295/401246 öyle değil, çünkü" en iyi çözümdür: a) (bile değil performans düşüşü dahil nullbir LCD bütün döngü genelleme) Enumerablekullanma gibi ( ??olur ), b) her Projeye bir Uzatma Yöntemi eklemeyi gerektirir veya c) null IEnumerables (Pffft! Puh-LEAZE! SMH.) ile başlamaktan kaçınmayı gerektirir (cuz, nullN / A anlamına gelir, oysa boş liste, geçerlidir, ancak şu anda, yani boş !, yani bir Çalışan, Satış dışı olanlar için N / A veya hiç kazanmadıkları halde Satışlar için boş olan Komisyonlara sahip olabilir).
Tom

Yanıtlar:


123

Yine de (items! = Null) olup olmadığını kontrol etmeniz gerekir, aksi takdirde NullReferenceException alırsınız. Ancak bunun gibi bir şey yapabilirsiniz:

List<string> items = null;  
foreach (var item in items ?? new List<string>())
{
    item.Dump();
}

ancak performansını kontrol edebilirsiniz. Bu yüzden hala önce if (items! = Null) olmasını tercih ediyorum.

Eric'in Lippert önerisine dayanarak kodu şu şekilde değiştirdim:

List<string> items = null;  
foreach (var item in items ?? Enumerable.Empty<string>())
{
    item.Dump();
}

33
Sevimli fikir; daha az bellek tükettiği ve daha az bellek baskısı ürettiği için boş bir dizi tercih edilir. Enumerable.Empty <string>, oluşturduğu boş diziyi önbelleğe alıp yeniden kullandığı için daha da tercih edilir.
Eric Lippert

6
İkinci kodun daha yavaş olmasını bekliyorum. Bir dizilimi dejenere IEnumerable<T>yavaş iterasyon hale arabirime numaralandırıcıya bu da bozulur. Testim, bir int dizisi üzerinde yineleme için bir faktör 5 bozulması gösterdi.
CodesInChaos

12
@CodeInChaos: Genellikle boş bir diziyi numaralandırma hızının programınızdaki performans darboğazı olduğunu düşünüyor musunuz?
Eric Lippert

16
Yalnızca boş dizinin numaralandırma hızını değil, aynı zamanda tüm diziyi de azaltır. Ve sıra yeterince uzunsa önemli olabilir. Çoğu kod için deyimsel kodu seçmeliyiz. Ancak bahsettiğiniz iki tahsis, daha da az durumda bir performans sorunu olacaktır.
CodesInChaos

17
@CodeInChaos: Ah, demek istediğini şimdi anlıyorum. Derleyici "foreach" ın bir List <T> veya bir dizi üzerinde yinelendiğini algıladığında, foreach'i değer türü numaralandırıcıları kullanmak veya gerçekten bir "for" döngüsü oluşturmak için optimize edebilir. Ya bir liste ya da boş sıra üzerinden numaralandırmaya zorlandığında , bazı durumlarda daha yavaş olabilecek ve daha fazla bellek baskısı oluşturabilen "en düşük ortak payda" kod oluşturucusuna geri dönmelidir. Bu ince ama mükemmel bir noktadır. Tabii ki, hikayenin ahlaki yönü - her zaman olduğu gibi - mükemmel bir sorununuz varsa, o zaman gerçek darboğazın ne olduğunu bulmak için onun profilini çıkarın.
Eric Lippert

75

C # 6'yı kullanarak, yeni boş koşullu operatörü List<T>.ForEach(Action<T>)(veya kendi IEnumerable<T>.ForEachgenişletme yönteminizle) birlikte kullanabilirsiniz.

List<string> items = null;
items?.ForEach(item =>
{
    // ...
});

Zarif cevap. Teşekkürler!
Steve

2
Bu en iyi çözümdür, çünkü: a) nulltüm döngünün LCD'ye genelleştirilmesinin (olmasa bile) performans düşüşünü içermesi Enumerable(kullanılacağı gibi ??), b) her Projeye bir Uzatma Yöntemi eklemeyi gerektirmesi veya c ) null IEnumerables (Pffft! Puh-LEAZE! SMH.) ile başlamaktan kaçınmayı gerektirme (cuz, nullN / A anlamına gelir, oysa boş liste, geçerlidir, ancak şu anda, iyi, boş ! anlamına gelir , yani bir Çalışan , şu Komisyonlara sahip olabilir Satış yapmayanlar için Yok veya hiç kazanmadıklarında Satışlar için boş).
Tom

7
@Tom: Bunun herhangi itemsbir şeyden List<T>ziyade bir düşünce olduğunu varsayar IEnumerable<T>. (Veya olmasını istemediğinizi söylediğiniz özel bir uzatma yöntemine sahip olun ...) Ek olarak, temelde belirli bir yanıtı beğendiğinizi söyleyen 11 yorum eklemeye değmeyeceğini söyleyebilirim .
Jon Skeet

3
@Tom: Seni gelecekte bunu yapmaktan kesinlikle vazgeçiririm. Yorumunuza karşı çıkan herkes o kadar kendi yorumlarını ekledi düşünün senin bütün . (Cevabımı buraya 11 kez yazdığımı düşünün.) Bu, Stack Overflow'un verimli bir kullanımı değildir.
Jon Skeet

2
Ayrıca, bir standarda karşı delegeyi arayan bir performans vuruşu olacağını varsayıyorum foreach. Özellikle bir fordöngüye dönüştürüleceğini düşündüğüm bir Liste için .
kjbartel

38

Buradaki gerçek paket servis , ilk etapta neredeyse hiçbir zaman boş olmamalı bir dizi olmalıdır . Basitçe onu tüm programlarınızda bir değişmez yapın, eğer bir sıranız varsa, asla boş olmaz. Her zaman boş sıra veya başka bir gerçek sıra olarak başlatılır.

Bir dizi hiçbir zaman boş değilse, açıkça kontrol etmenize gerek yoktur.


2
Sırayı bir WCF hizmetinden alırsanız nasıl olur? Boş olabilir, değil mi?
Nawaz

4
@Nawaz: Boş diziler olmasını amaçlayan boş diziler döndüren bir WCF hizmetim olsaydı, bunu onlara bir hata olarak bildirirdim. Bununla birlikte, tartışmalı hatalı hizmetlerin kötü biçimlendirilmiş çıktılarıyla uğraşmak zorunda kalırsanız, evet, bununla null olup olmadığını kontrol etmelisiniz.
Eric Lippert

7
Tabii ki boş ve boş tamamen farklı şeyler ifade etmedikçe. Bazen bu sekanslar için geçerlidir.
konfigüratör

@Nawaz Boş bir koleksiyon yerine null döndüren DataTable.Rows hakkında. Belki bu bir hata?
Neil B

@ kjbartel cevabı ( "at stackoverflow.com/a/32134295/401246 öyle değil, çünkü" en iyi çözümdür: a) (bile değil performans düşüşü dahil nullbir LCD bütün döngü genelleme) Enumerablekullanma gibi ( ??olur ), b) her Projeye bir Uzatma Yöntemi eklemeyi gerektirir veya c) null IEnumerables (Pffft! Puh-LEAZE! SMH.) ile başlamaktan kaçınmayı gerektirir (cuz, nullN / A anlamına gelir, oysa boş liste, geçerlidir, ancak şu anda, yani boş !, yani bir Çalışan, Satış dışı olanlar için N / A veya hiç kazanmadıkları halde Satışlar için boş olan Komisyonlara sahip olabilir).
Tom

10

Aslında @Connect'te bir özellik isteği var: http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null

Ve yanıt oldukça mantıklı:

Çoğu foreach döngüsünün boş olmayan bir koleksiyonu yineleme amacıyla yazıldığını düşünüyorum. Null ile yinelemeyi denerseniz, istisnanızı almalısınız, böylece kodunuzu düzeltebilirsiniz.


Sanırım bunun artıları ve eksileri var, bu yüzden ilk başta tasarlandığı gibi tutmaya karar verdiler. sonuçta foreach sadece bir miktar sözdizimsel şekerdir. Maddeler boş olsaydı da çökecek olan items.GetEnumerator () 'ı çağırsaydınız, bu yüzden önce onu test etmeniz gerekiyordu.
Marius Bancila

6

Her zaman boş bir liste ile test edebilirsiniz ... ama msdn web sitesinde bulduğum şey bu

foreach-statement:
    foreach   (   type   identifier   in   expression   )   embedded-statement 

İfade null değerine sahipse, bir System.NullReferenceException oluşturulur.


2

Gereksiz değil. Çalışma zamanında öğeler bir IEnumerable'a dönüştürülecek ve GetEnumerator yöntemi çağrılacaktır. Bu, başarısız olacak öğelerin referans alınmasına neden olur


1
1) Dizi mutlaka atanmayacaktır IEnumerableve 2) Attırmak bir tasarım kararıdır. C # null, geliştiriciler bunu iyi bir fikir olarak gördüyse, bu denetimi kolayca ekleyebilir .
CodesInChaos

2

Boş denetimi bir uzantı yönteminde kapsülleyebilir ve bir lambda kullanabilirsiniz:

public static class EnumerableExtensions {
  public static void ForEach<T>(this IEnumerable<T> self, Action<T> action) {
    if (self != null) {
      foreach (var element in self) {
        action(element);
      }
    }
  }
}

Kod şu hale gelir:

items.ForEach(item => { 
  ...
});

Sadece bir öğeyi alıp geri döndüren bir yöntemi çağırmak istiyorsanız daha da kısa olabilir void:

items.ForEach(MethodThatTakesAnItem);

1

Buna ihtiyacın var. foreachAksi takdirde yinelemeyi ayarlamak için konteynere eriştiğinizde bir istisna alırsınız .

Kapaklar altında, yinelemeyi gerçekleştirmek için koleksiyon sınıfında uygulanan bir arabirimforeach kullanır . Jenerik eşdeğer arayüz burada .

C # dilinin foreach ifadesi (Visual Basic'teki her biri için) numaralandırıcıların karmaşıklığını gizler. Bu nedenle, numaralandırıcıyı doğrudan manipüle etmek yerine foreach kullanılması önerilir.


1
Arayüzü teknik olarak kullanmadığı bir not gibi, ördek yazmayı kullanır: blogs.msdn.com/b/kcwalina/archive/2007/07/18/ducknotation.aspx arayüzler doğru yöntemlerin ve özelliklerin orada olmasını sağlar yine de ve niyetin anlaşılmasına yardımcı olun. ve foreach dışında kullanım ...
ShuggyCoUk

0

Test gereklidir, çünkü koleksiyon boşsa, foreach bir NullReferenceException oluşturur. Denemek aslında oldukça basit.

List<string> items = null;
foreach(var item in items)
{
   Console.WriteLine(item);
}

0

ikincisi NullReferenceExceptionmesajla birlikte bir atacakObject reference not set to an instance of an object.


0

Belirtildiği gibi burada bunu boş değil kontrol etmeniz gerekir.

Null olarak değerlendirilen bir ifade kullanmayın.


0

C # 6'da şu şekilde sth yazabilirsiniz:

// some string from file or UI, i.e.:
// a) string s = "Hello, World!";
// b) string s = "";
// ...
var items = s?.Split(new char[] { ',', '!', ' ' }) ?? Enumerable.Empty<string>();  
foreach (var item in items)
{
    //..
}

Temelde Vlad Bezden'in çözümü ama ?? Her zaman boş olmayan ve bu nedenle foreach köşeli parantez içinde bu denetimi yapmak yerine foreach'den sağ kalan bir dizi oluşturmak için ifade.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.