Boş veya boş koleksiyon döndürmek daha mı iyi?


420

Bu genel bir soru (ama C # kullanıyorum), en iyi yol nedir (en iyi uygulama), dönüş türü olarak bir koleksiyonu olan bir yöntem için boş veya boş toplama döndürüyor musunuz?


5
Um, tam olarak CPerkins değil. rads.stackoverflow.com/amzn/click/0321545613

3
@CPerkins - Evet, var. Microsoft'un kendi .NET framework tasarım kılavuzlarında açıkça belirtilmiştir. Ayrıntılar için RichardOD'nin cevabına bakınız.
Greg Beech

51
SADECE "Eğer sonuçları hesaplayamıyorum" ise null değerini döndürürseniz. Null hiçbir zaman "boş", sadece "eksik" veya "bilinmeyen" anlamlarına sahip olmamalıdır. Konuyla ilgili makalemde daha fazla ayrıntı: blogs.msdn.com/ericlippert/archive/2009/05/14/…
Eric Lippert

3
Bozho: "herhangi" çok fazla dildir. Boş listenin null değerine tam olarak eşit olduğu Common Lisp ne olacak? :-)
Ken

9
Aslında, "yinelenen" bir koleksiyon değil, bir nesne döndüren yöntemlerle ilgilidir. Farklı cevaplara sahip farklı bir senaryo.
GalacticCowboy

Yanıtlar:


499

Boş toplama. Her zaman.

Bu berbat:

if(myInstance.CollectionProperty != null)
{
  foreach(var item in myInstance.CollectionProperty)
    /* arrgh */
}

nullBir koleksiyonu iade ederken veya numaralandırılırken ASLA geri dönmek için en iyi uygulama olarak kabul edilir . DAİMA boş bir numaralandırılabilir / koleksiyon döndürür. Söz konusu saçmalığı önler ve aracınızın meslektaşlarınız ve sınıflarınızın kullanıcıları tarafından engellenmesini önler.

Mülkler hakkında konuşurken, mülkünüzü her zaman bir kez ayarlayın ve unutun

public List<Foo> Foos {public get; private set;}

public Bar() { Foos = new List<Foo>(); }

.NET 4.6.1'de bunu oldukça yoğunlaştırabilirsiniz:

public List<Foo> Foos { get; } = new List<Foo>();

Numaralandırılabilir döndüren yöntemler hakkında konuşurken, yerine boş bir numaralandırılabilir kolayca dönebilirsiniz null...

public IEnumerable<Foo> GetMyFoos()
{
  return InnerGetFoos() ?? Enumerable.Empty<Foo>();
}

Kullanmak Enumerable.Empty<T>(), örneğin yeni bir boş koleksiyon veya dizi döndürmekten daha verimli olarak görülebilir.


24
Will'e katılıyorum, ama bence "her zaman" biraz fazladır. Boş bir koleksiyon "0 öğe" anlamına gelse de, Null döndürmek "hiç koleksiyon yok" anlamına gelebilir - ör. HTML'yi ayrıştırıyorsanız, id = "foo" ile bir <ul> arıyorsanız, <ul id = "foo"> </ul> boş koleksiyon döndürebilir; id = "foo" ile <ul> yoksa, boş bir geri dönüş daha iyi olurdu (bu durumu bir istisna dışında ele almak istemiyorsanız)
Patonza

30
her zaman "boş bir diziyi kolayca döndürüp döndüremeyeceğiniz" değil, boş bir dizinin geçerli bağlamda yanıltıcı olup olmayacağı sorusu değildir. Boş bir dizi aslında null gibi bir şey ifade eder. Her zaman null yerine boş bir dizi döndürmeniz gerektiğini söylemek, neredeyse bir boolean yönteminin her zaman true döndürmesi gerektiğini söylemek kadar yanlış yönlendirilmiştir. Her iki olası değer de bir anlam ifade eder.
David Hedlund

31
Aslında yeni bir Foo [0] yerine System.Linq.Enumerable.Empty <Foo> () döndürmeyi tercih etmelisiniz. Daha açık ve size bir bellek ayırma kaydeder (en azından benim yüklü .NET uygulama).
Trillian

4
@Will: OP: " dönüş türü olarak koleksiyonu olan bir yöntem için null veya boş koleksiyon döndürür müsünüz ". Bu ister bu durumda düşünmek IEnumerableveya ICollectiono kadar önemli değildir. Her neyse, tür bir şey seçerseniz ICollectiononlar da geri dönüyorlar null... Boş bir koleksiyon geri dönmelerini isterdim, ama geri dönenlere koştum null, bu yüzden burada bahsettiğimi düşündüm. Numaralandırılabilir bir koleksiyonun varsayılan değerinin boş olmadığını söyleyebilirim. Çok hassas bir konu olduğunu bilmiyordum.
Matthijs Wessels


154

Gönderen Çerçeve Tasarım Kuralları 2. Baskı (pg 256.):

Koleksiyon özelliklerinden veya koleksiyon döndüren yöntemlerden boş değerler döndürmeyin. Bunun yerine boş bir koleksiyon veya boş bir dizi döndürün.

Burada null'ları döndürmemenin yararları hakkında ilginç bir makale daha var (Brad Abram'ın blogunda bir şeyler bulmaya çalışıyordum ve makaleye bağlandı).

Düzenleme - Eric Lippert'in orijinal soruya yorum yaptığı gibi , mükemmel makalesine de bağlantı vermek istiyorum .


33
+1. Yapmamak için çok iyi bir nedeniniz yoksa, çerçeve tasarım yönergelerine uymak her zaman en iyi uygulamadır.

@ Kesinlikle. Ben de bunu takip ediyorum. Hiçbir zaman başka türlü bir şey yapmadım
RichardOD

evet, takip ettiğin şey bu. Ancak, güvendiğiniz API'ların takip etmediği durumlarda, 'tuzağa
düştünüz

@ Bozho- yeap. Cevabınız bu saçak vakaların güzel cevaplarını veriyor.
RichardOD

90

Senin bağlıdır sözleşme ve somut durumda . Genellikle boş koleksiyonları iade etmek en iyisidir , ancak bazen ( nadiren ):

  • null daha spesifik bir şey anlamına gelebilir;
  • API'nız (sözleşmeniz) sizi geri dönmeye zorlayabilir null.

Bazı somut örnekler:

  • bir UI bileşeni (kontrolünüz dışındaki bir kitaplıktan), boş bir koleksiyon geçirilirse boş bir tablo oluşturabilir veya null iletilirse hiç tablo olmaz.
  • bir XML'den Nesneye (JSON / ne olursa olsun), nullöğenin eksik olduğu anlamına gelirken, boş bir koleksiyon gereksiz (ve muhtemelen yanlış)<collection />
  • null değerinin döndürülmesi / geçirilmesi gerektiğini açıkça bildiren bir API kullanıyorsunuz veya uyguluyorsunuz

4
@ Bozho- bazı ilginç örnekler burada.
RichardOD

1
Sana diğer hata etrafında kod oluşturmak gerektiğini düşünmüyorum gibi biraz bakacağım ve c # 'tanımında null' olarak tanımlamak asla belirli bir şey anlamına gelmez . Null değeri "bilgi yok" olarak tanımlanır ve dolayısıyla belirli bir bilgi taşıdığını söylemek bir oksimodondur. Bu nedenle .NET yönergeleri, küme gerçekten boşsa boş bir küme döndürmeniz gerektiğini belirtir. null döndürme şöyle diyor: "Beklenen setin nereye gittiğini bilmiyorum"
Rune FS

13
hayır, "kümenin öğesi yok" yerine "küme yok" anlamına gelir
Bozho

Eskiden bir sürü TSQL yazmam gerektiğine inanıyordum ve bunun her zaman böyle olmadığını öğrendim heheh.

1
Object-To-Xml olan bölüm
etkindir

36

Henüz bahsedilmeyen başka bir nokta daha var. Aşağıdaki kodu göz önünde bulundurun:

    public static IEnumerable<string> GetFavoriteEmoSongs()
    {
        yield break;
    }

Bu yöntemi çağırırken C # Dili boş bir numaralandırıcı döndürür. Bu nedenle, dil tasarımına (ve dolayısıyla programcı beklentilerine) uymak için boş bir koleksiyon iade edilmelidir.


1
Teknik olarak bunun boş bir koleksiyon döndürdüğünü düşünmüyorum.
FryGuy

3
@FryGuy - Hayır değil. GetEnumerator () yöntemi (bir koleksiyonla aynı şekilde) boş bir Enumerator döndüren bir Enumerable nesnesi döndürür. Yani, Numaralandırıcı'nın MoveNext () yöntemi her zaman false değerini döndürür. Örnek yöntemin çağrılması null değerini döndürmez veya GetEnumerator () yöntemi null döndüren bir Enumerable nesnesi döndürmez.
Jeffrey L Whitledge

31

Boş daha tüketici dostu.

Boş bir numaralandırılabilir yapmak için açık bir yöntem vardır:

Enumerable.Empty<Element>()

2
Düzenli numara için teşekkürler. Bir aptal gibi boş bir List <T> () somutlaştırıyordum ama bu çok daha temiz görünüyor ve muhtemelen biraz daha verimli.
Jacobs Data Solutions

18

Bana öyle geliyor ki, bağlamsal olarak anlamsal olarak doğru olan değeri, ne olursa olsun döndürmelisiniz. "Her zaman boş bir koleksiyon iade edin" yazan bir kural benim için biraz basit görünüyor.

Diyelim ki, bir hastane sistemi için, son 5 yıl için önceki tüm hastaneye yatışların bir listesini döndürmesi gereken bir fonksiyonumuz var. Müşteri hastaneye gitmediyse boş bir listeye geri dönmek mantıklıdır. Peki ya müşteri başvuru formunun bu kısmını boş bırakırsa? "Boş liste" yi "yanıt yok" veya "bilmiyorum" ifadesinden ayırmak için farklı bir değere ihtiyacımız var. Bir istisna atabiliriz, ancak bu mutlaka bir hata koşulu değildir ve bizi normal program akışından çıkarmayabilir.

Sık sık sıfır ile cevap veremeyen sistemleri ayırt edemiyorum. Bir sistemin bir sayı girmemi istediği birkaç kez yaşadım, sıfır giriyorum ve bu alana bir değer girmem gerektiğini söyleyen bir hata mesajı alıyorum. Az önce yaptım: Sıfır girdim! Ama sıfırı kabul etmez, çünkü cevabı hiçbirinden ayırt edemez.


Saunders Cevapla:

Evet, "Kişi soruya cevap vermedi" ile "Cevap sıfırdı" arasında bir fark olduğunu varsayıyorum. Cevabımın son paragrafının noktası buydu. Birçok program "bilmiyorum" u boş veya sıfırdan ayıramaz, bu da bana potansiyel olarak ciddi bir kusur gibi görünüyor. Örneğin, bir yıl kadar önce bir ev için alışveriş yapıyordum. Bir emlak web sitesine gittim ve 0 $ 'lık bir fiyatla listelenen birçok ev vardı. Bana çok iyi geldi: Bu evleri bedava veriyorlar! Ama eminim ki üzücü gerçek şu ki fiyatına girmemiş olmalarıydı. Bu durumda, "EĞER, ZORUNLU sıfır, fiyata girmedikleri anlamına gelir - hiç kimse ücretsiz bir ev veremez." Ancak site aynı zamanda çeşitli kasabalardaki evlerin ortalama soran ve satış fiyatlarını da listeledi. Yardım edemem ama ortalama sıfırlar dahil olmadığını merak ediyorum, böylece bazı yerler için yanlış düşük bir ortalama vererek. yani ortalama 100.000 dolar nedir; 120.000 $; ve "bilmiyorum"? Teknik olarak cevap "bilmiyorum". Muhtemelen gerçekten görmek istediğimiz şey 110.000 dolar. Ama muhtemelen alacağımız 73.333 $, ki bu tamamen yanlış olurdu. Ayrıca, kullanıcıların çevrimiçi olarak sipariş verebilecekleri bir sitede bu sorunu yaşarsak ne olur? (Emlak için olası değil, ama eminim diğer birçok ürün için yapıldığını gördüm.) Gerçekten "henüz belirtilmemiş fiyat" "ücretsiz" olarak yorumlanmasını ister misiniz? böylece bazı yerler için yanlış düşük bir ortalama verir. yani ortalama 100.000 dolar nedir; 120.000 $; ve "bilmiyorum"? Teknik olarak cevap "bilmiyorum". Muhtemelen gerçekten görmek istediğimiz şey 110.000 dolar. Ama muhtemelen alacağımız 73.333 $, ki bu tamamen yanlış olurdu. Ayrıca, kullanıcıların çevrimiçi olarak sipariş verebilecekleri bir sitede bu sorunu yaşarsak ne olur? (Emlak için olası değil, ama eminim diğer birçok ürün için yapıldığını gördüm.) Gerçekten "henüz belirtilmemiş fiyat" "ücretsiz" olarak yorumlanmasını ister misiniz? böylece bazı yerler için yanlış düşük bir ortalama verir. yani ortalama 100.000 dolar nedir; 120.000 $; ve "bilmiyorum"? Teknik olarak cevap "bilmiyorum". Muhtemelen gerçekten görmek istediğimiz şey 110.000 dolar. Ama muhtemelen alacağımız 73.333 $, ki bu tamamen yanlış olurdu. Ayrıca, kullanıcıların çevrimiçi olarak sipariş verebilecekleri bir sitede bu sorunu yaşarsak ne olur? (Emlak için olası değil, ama eminim diğer birçok ürün için yapıldığını gördüm.) Gerçekten "henüz belirtilmemiş fiyat" "ücretsiz" olarak yorumlanmasını ister misiniz? ki bu tamamen yanlış olur. Ayrıca, kullanıcıların çevrimiçi olarak sipariş verebilecekleri bir sitede bu sorunu yaşarsak ne olur? (Emlak için olası değil, ama eminim diğer birçok ürün için yapıldığını gördüm.) Gerçekten "henüz belirtilmemiş fiyat" "ücretsiz" olarak yorumlanmasını ister misiniz? ki bu tamamen yanlış olur. Ayrıca, kullanıcıların çevrimiçi olarak sipariş verebilecekleri bir sitede bu sorunu yaşarsak ne olur? (Emlak için olası değil, ama eminim diğer birçok ürün için yapıldığını gördüm.) Gerçekten "henüz belirtilmemiş fiyat" "ücretsiz" olarak yorumlanmasını ister misiniz?

RE'nin iki ayrı işlevi vardır, bir "var mı?" ve "eğer öyleyse, nedir?" Evet, kesinlikle yapabilirsin, ama neden yapmak istersin? Şimdi çağrı programı bir yerine iki çağrı yapmak zorunda. Bir programcı "herhangi biri" olarak adlandırmazsa ne olur? ve doğrudan "bu nedir?" ? Program yanlış yönlendiren bir sıfır döndürür mü? İstisna atmak mı? Tanımlanmamış bir değer döndürülsün mü? Daha fazla kod, daha fazla çalışma ve daha fazla potansiyel hata oluşturur.

Gördüğüm tek fayda, keyfi bir kurala uymanızı sağlaması. Bu kurala uyma zahmetine değecek bir avantaj var mı? Değilse, neden rahatsız oluyorsun?


Jammycakes için yanıt:

Gerçek kodun nasıl görüneceğini düşünün. Sorunun C # dediğini biliyorum ama Java yazdığımda izin verin. C #'m çok keskin değil ve prensip aynı.

Boş bir dönüşle:

HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
   // ... handle missing list ...
}
else
{
  for (HospEntry entry : list)
   //  ... do whatever ...
}

Ayrı bir işlevle:

if (patient.hasHospitalizationList(patientId))
{
   // ... handle missing list ...
}
else
{
  HospList=patient.getHospitalizationList(patientId))
  for (HospEntry entry : list)
   // ... do whatever ...
}

Aslında null dönüşü olan bir satır veya iki daha az kod, bu yüzden arayan için daha fazla yük değil, daha az.

Nasıl KURU bir sorun oluşturduğunu görmüyorum. Çağrıyı iki kez yapmak zorunda değiliz. Liste olmadığında her zaman aynı şeyi yapmak isteseydik, belki de arayanın bunu yapmaktan ziyade get-list işlevine devam etmeyi zorlayabiliriz ve bu nedenle kodu arayana koymak KURU ihlali olur. Ama neredeyse her zaman aynı şeyi yapmak istemiyoruz. İşlenecek listeye sahip olmamız gereken işlevlerde, eksik bir liste işlenmeyi durdurabilecek bir hatadır. Ancak bir düzenleme ekranında, henüz veri girmemişlerse işlemeyi durdurmak istemiyoruz: veri girmelerine izin vermek istiyoruz. Bu nedenle "liste yok" işleminin arayan düzeyinde şu veya bu şekilde yapılması gerekir. Ve bunu boş bir dönüşle mi yoksa ayrı bir işlevle mi yapacağız, daha büyük prensipte hiçbir fark yaratmaz.

Elbette, arayan null olup olmadığını kontrol etmezse, program null-pointer istisnası ile başarısız olabilir. Ancak ayrı bir "var" işlevi varsa ve arayan kişi bu işlevi çağırmaz, ancak körü körüne "listeyi al" işlevini çağırırsa ne olur? Bir istisna atarsa ​​veya başka bir şekilde başarısız olursa, bu, null döndürdüğünde ve kontrol etmediyse ne olacağıyla hemen hemen aynıdır. Boş bir liste döndürürse, bu sadece yanlış. "Sıfır öğeli bir listem var" ile "listem yok" arasında ayrım yapmıyorsunuz. Kullanıcı herhangi bir fiyat girmediğinde fiyat için sıfır döndürmek gibi: sadece yanlış.

Koleksiyona ek bir özellik eklemenin nasıl yardımcı olduğunu görmüyorum. Arayan hala kontrol etmelidir. Boş değeri kontrol etmekten daha iyi ne olabilir? Yine, olabilecek en kötü şey, programcının kontrol etmeyi unutması ve yanlış sonuçlar vermesidir.

Null döndüren bir işlev, programcı null anlamı "değeri yok" kavramına aşina ise sürpriz değildir, ki herhangi bir yetkili programcının iyi bir fikir olup olmadığını düşünüp düşünmediğini düşünmesi gerektiğini düşünüyorum. Ayrı bir işleve sahip olmanın daha çok "sürpriz" bir sorun olduğunu düşünüyorum. Bir programcı API'yı bilmiyorsa, veri içermeyen bir test çalıştırdığında, bazen boş bir değer aldığını hızlı bir şekilde keşfeder. Fakat böyle bir fonksiyon olabileceği ve dokümantasyonu kontrol ettiği ve dokümantasyonun tam ve anlaşılabilir olmadığı sürece başka bir fonksiyonun varlığını nasıl keşfeder? Her ikisini de aramam gerektiğini bilmem ve hatırlamam gereken iki işlev yerine, bana her zaman anlamlı bir yanıt veren bir işlevim olmasını tercih ederim.


4
"Yanıt yok" ve "sıfır" yanıtlarını aynı dönüş değerinde birleştirmek neden gereklidir? Bunun yerine, yöntemin "son beş yıl içinde herhangi bir önceki hastaneye yatış" ı döndürmesini ve "önceki hastaneye yatışlar listesi hiç doldurulmuş mu?" Bu, daha önce hastaneye yatırılmadan doldurulmuş bir liste ile doldurulmamış bir liste arasında bir fark olduğunu varsayar.
John Saunders

2
Ama null döndürürseniz zaten zaten arayan üzerinde ekstra bir yük yerleştiriyorsunuz! Arayanın her dönüş değerini null için kontrol etmesi gerekir - korkunç bir KURU ihlali. Eğer belirtmek istiyorsanız ayrı ayrı, "arayan soruya cevap vermedi" olduğu gerçeğini belirtmek için fazladan bir yöntem çağrısı oluşturmak için daha basit. Ya bu, ya da ek bir DeclinedToAnswer özelliğine sahip türetilmiş bir toplama türü kullanın. Hiçbir zaman sıfır döndürmeme kuralı hiç de keyfi değildir, En Az Sürpriz İlkesi'dir. Ayrıca, yönteminizin dönüş türü, adının söylediği anlamına gelmelidir. Null neredeyse kesinlikle yapmaz.
jammycakes

2
GetHospitalizationList'inizin yalnızca bir yerden çağrıldığını ve / veya tüm arayanlarının "yanıt yok" ve "sıfır" durumlarını ayırt etmek isteyeceğini varsayıyorsunuz. Orada olacak vakalar bu gerekli olmamalıdır yerlerde boş çekler eklemek için onları zorluyor böylece arayan o ayrımı yapmak gerekir ve yok (neredeyse kesin bir çoğunluk). Bu, kod tabanınıza önemli bir risk katıyor, çünkü insanlar bunu yapmayı çok kolay unutabilirler - ve bir koleksiyon yerine null değerini döndürmenin çok az meşru nedeni olduğundan, bu çok daha olası olacaktır.
jammycakes

3
RE adı: Hiçbir işlev adı, işlev sürece sürece işlevin ne yaptığını tam olarak açıklayamaz ve dolayısıyla çılgınca pratik değildir. Ancak her durumda, herhangi bir cevap verilmediğinde fonksiyon boş bir liste döndürürse, aynı mantıkla "getHospitalizationListOrEmptyListIfNoAnswer" olarak adlandırılmamalı mı? Ama gerçekten, Java Reader.read işlevinin readCharacterOrReturnMinusOneOnEndOfStream olarak yeniden adlandırılması konusunda ısrar eder misiniz? Bu ResultSet.getInt gerçekten "getIntOrZeroIfValueWasNull" olmalıdır? Vb
Jay

4
RE her çağrı ayırt etmek istiyor: Evet, evet, ya da en azından bir arayanın yazarının umursamadığı bilinçli bir karar vermesi gerektiğini varsayıyorum. İşlev "bilmiyorum" için boş bir liste döndürürse ve arayanlar bu "hiçbirine" körü körüne davranırsa, ciddi yanlış sonuçlar verebilir. İşlevin "getAllergicReactionToMedicationList" olup olmadığını düşünün. "Hastanın bilinen hiçbir alerjik reaksiyonu olmadığı" şeklinde "körü körüne tedavi edilen bir program girilmedi" kelimenin tam anlamıyla bir kişinin öldürülmesine neden olabilir. Diğer birçok sistemde daha az dramatik sonuçlar elde ederseniz benzerlik elde edersiniz. ...
Jay

10

Boş bir koleksiyon semantik olarak anlamlıysa, geri dönmeyi tercih ederim. GetMessagesInMyInbox()İletişim için boş bir koleksiyon döndürmek, "gerçekten gelen kutunuzda hiç mesajınız yok" iletmekle birlikte, geri dönüş, geri nulldöndürülebilecek listenin neye benzemesi gerektiğini söylemek için yeterli veri olmadığını bildirmek için yararlı olabilir.


6
Verdiğiniz örnekte, yöntemin yalnızca null döndürmek yerine isteği yerine getirememesi durumunda bir istisna atması gerektiği anlaşılıyor. İstisnalar, sorunların teşhisi için null değerlerden çok daha yararlıdır.
Greg Beech

iyi evet, gelen kutusunda örnek bir nulldeğer kesinlikle makul görünmüyor, ben bu konuda daha genel anlamda düşünüyordum. İstisnalar da bir şeyin yanlış gittiğini bildirmek için harikadır, ancak atıfta bulunulan "yetersiz veri" mükemmel bir şekilde bekleniyorsa, bir istisna atmak kötü tasarım olacaktır. Oldukça mükemmel bir şekilde mümkün olduğu ve bazen bir yanıt hesaplamak mümkün değildir yöntemi için hiçbir hata nerede bir senaryo düşünüyorum.
David Hedlund

6

Yeni bir nesne oluşturulmadığından, null değerini döndürmek daha verimli olabilir. Bununla birlikte, genellikle bir nullkontrol (veya istisna işleme) de gerektirir .

Anlamsal olarak nullboş bir liste aynı anlama gelmez. Farklılıklar belirsizdir ve belirli durumlarda bir seçenek diğerinden daha iyi olabilir.

Seçiminiz ne olursa olsun, karışıklığı önlemek için belgeyi belgeleyin.


8
Bir API tasarımının doğruluğu düşünüldüğünde verimlilik neredeyse hiçbir zaman bir faktör olmamalıdır. Grafik ilkelleri gibi bazı çok özel durumlarda, öyle olabilir, ancak listeler ve diğer yüksek seviyeli şeylerle uğraşırken bundan çok şüpheliyim.
Greg Beech

Greg ile aynı fikirde olun, özellikle API kullanıcısının bu "optimizasyonu" telafi etmek için yazmak zorunda olduğu kod ilk etapta daha iyi bir tasarımın kullanılmasından daha verimsiz olabilir.
Craig Stuntz

Kabul etti ve çoğu durumda optimizasyona değmez. Boş listeler modern bellek yönetimi ile pratik olarak ücretsizdir.
Jason Baker

6

Boş Nesne Deseni'nin arkasındaki mantığın , boş koleksiyonu geri döndürme lehine benzer olduğu iddia edilebilir.


4

Duruma göre. Bu özel bir durumsa, null değerini döndürün. Eğer fonksiyon sadece boş bir koleksiyon döndürürse, o zaman tabii ki geri dönüyor. Ancak, boş parametrelerin geçersiz parametreler veya diğer nedenlerden dolayı özel bir durum olarak döndürülmesi iyi bir fikir DEĞİLDİR, çünkü özel durum durumunu maskelemektedir.

Aslında, bu durumda GERÇEKTEN göz ardı edilmediğinden emin olmak için genellikle bir istisna atmayı tercih ederim :)

Kodun daha sağlam hale getirildiğini söylemek (boş bir koleksiyon döndürerek), boş koşulu işlemek zorunda olmadıkları için kötüdür, çünkü sadece çağrı kodu tarafından ele alınması gereken bir sorunu maskelemektedir.


4

Bunun nullboş bir koleksiyonla aynı şey olmadığını ve hangisinin geri döndüğünüzü en iyi temsil ettiğini seçmelisiniz. Çoğu durumda null(SQL hariç) hiçbir şey yoktur. Boş bir koleksiyon, boş bir şey de olsa bir şeydir.

Birini ya da diğerini seçmek zorunda kalırsanız, null yerine boş bir koleksiyona yönelmeniz gerektiğini söyleyebilirim. Ancak boş bir koleksiyonun null değerle aynı olmadığı zamanlar vardır.


4

Her zaman müşterilerinizi (API'nızı kullanan) lehine düşünün:

'Null' değerinin çok sık döndürülmesi, istemcilerin null denetimleri doğru işlememesi ile ilgili sorunlara neden olur ve bu da çalışma zamanı sırasında NullPointerException özelliğine neden olur. Böyle bir eksik null-kontrolün bir öncelikli üretim sorununu zorladığı durumlar gördüm (müşteri null değerinde foreach (...) kullandı). Test sırasında sorun oluşmadı, çünkü üzerinde çalışan veriler biraz farklıydı.


3

Burada uygun bir örnekle açıklama yapmayı seviyorum.

Burada bir dava düşünün ..

int totalValue = MySession.ListCustomerAccounts()
                          .FindAll(ac => ac.AccountHead.AccountHeadID 
                                         == accountHead.AccountHeadID)
                          .Sum(account => account.AccountValue);

İşte kullandığım fonksiyonları düşünün ..

1. ListCustomerAccounts() // User Defined
2. FindAll()              // Pre-defined Library Function

Kolayca kullanabilir ListCustomerAccountve FindAllyerine.,

int totalValue = 0; 
List<CustomerAccounts> custAccounts = ListCustomerAccounts();
if(custAccounts !=null ){
  List<CustomerAccounts> custAccountsFiltered = 
        custAccounts.FindAll(ac => ac.AccountHead.AccountHeadID 
                                   == accountHead.AccountHeadID );
   if(custAccountsFiltered != null)
      totalValue = custAccountsFiltered.Sum(account => 
                                            account.AccountValue).ToString();
}

Not: AccountValue olmadığından null, Sum () işlevi döndürmez null. Bu nedenle doğrudan kullanabilirsiniz.


2

Bu tartışmayı bir hafta kadar önce geliştirme ekibi arasında gerçekleştirdik ve neredeyse oybirliğiyle boş koleksiyona gittik. Bir kişi, Mike'ın yukarıda belirttiği nedenden dolayı null değerini döndürmek istedi.


2

Boş toplama. C # kullanıyorsanız, sistem kaynaklarını en üst düzeye çıkarmanın gerekli olmadığı varsayımıdır. Daha az verimli olsa da, Boş Koleksiyon'a dönmek, dahil olan programcılar için çok daha uygundur (bu nedenle yukarıda ana hatlarıyla belirtilecektir).


2

Boş bir koleksiyonu iade etmek çoğu durumda daha iyidir.

Bunun nedeni, arayanın uygulanmasının kolaylığı, tutarlı sözleşme ve daha kolay uygulamadır.

Bir yöntem boş sonucu belirtmek için null değerini döndürürse, arayan numaralandırmaya ek olarak bir null kontrol adaptörü de uygulamalıdır. Bu kod daha sonra çeşitli arayanlarda çoğaltılır, bu yüzden bu bağdaştırıcıyı yeniden kullanılabilir olması için yöntemin içine koymuyoruz.

IEnumerable için null değerinin geçerli bir kullanımı, eksik bir sonucun veya bir işlem arızasının göstergesi olabilir, ancak bu durumda istisna atma gibi diğer teknikler göz önünde bulundurulmalıdır.

using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace StackOverflow.EmptyCollectionUsageTests.Tests
{
    /// <summary>
    /// Demonstrates different approaches for empty collection results.
    /// </summary>
    class Container
    {
        /// <summary>
        /// Elements list.
        /// Not initialized to an empty collection here for the purpose of demonstration of usage along with <see cref="Populate"/> method.
        /// </summary>
        private List<Element> elements;

        /// <summary>
        /// Gets elements if any
        /// </summary>
        /// <returns>Returns elements or empty collection.</returns>
        public IEnumerable<Element> GetElements()
        {
            return elements ?? Enumerable.Empty<Element>();
        }

        /// <summary>
        /// Initializes the container with some results, if any.
        /// </summary>
        public void Populate()
        {
            elements = new List<Element>();
        }

        /// <summary>
        /// Gets elements. Throws <see cref="InvalidOperationException"/> if not populated.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>.</returns>
        public IEnumerable<Element> GetElementsStrict()
        {
            if (elements == null)
            {
                throw new InvalidOperationException("You must call Populate before calling this method.");
            }

            return elements;
        }

        /// <summary>
        /// Gets elements, empty collection or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with zero or more elements, or null in some cases.</returns>
        public IEnumerable<Element> GetElementsInconvenientCareless()
        {
            return elements;
        }

        /// <summary>
        /// Gets elements or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with elements, or null in case of empty collection.</returns>
        /// <remarks>We are lucky that elements is a List, otherwise enumeration would be needed.</remarks>
        public IEnumerable<Element> GetElementsInconvenientCarefull()
        {
            if (elements == null || elements.Count == 0)
            {
                return null;
            }
            return elements;
        }
    }

    class Element
    {
    }

    /// <summary>
    /// http://stackoverflow.com/questions/1969993/is-it-better-to-return-null-or-empty-collection/
    /// </summary>
    class EmptyCollectionTests
    {
        private Container container;

        [SetUp]
        public void SetUp()
        {
            container = new Container();
        }

        /// <summary>
        /// Forgiving contract - caller does not have to implement null check in addition to enumeration.
        /// </summary>
        [Test]
        public void UseGetElements()
        {
            Assert.AreEqual(0, container.GetElements().Count());
        }

        /// <summary>
        /// Forget to <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void WrongUseOfStrictContract()
        {
            container.GetElementsStrict().Count();
        }

        /// <summary>
        /// Call <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        public void CorrectUsaOfStrictContract()
        {
            container.Populate();
            Assert.AreEqual(0, container.GetElementsStrict().Count());
        }

        /// <summary>
        /// Inconvenient contract - needs a local variable.
        /// </summary>
        [Test]
        public void CarefulUseOfCarelessMethod()
        {
            var elements = container.GetElementsInconvenientCareless();
            Assert.AreEqual(0, elements == null ? 0 : elements.Count());
        }

        /// <summary>
        /// Inconvenient contract - duplicate call in order to use in context of an single expression.
        /// </summary>
        [Test]
        public void LameCarefulUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless() == null ? 0 : container.GetElementsInconvenientCareless().Count());
        }

        [Test]
        public void LuckyCarelessUseOfCarelessMethod()
        {
            // INIT
            var praySomeoneCalledPopulateBefore = (Action)(()=>container.Populate());
            praySomeoneCalledPopulateBefore();

            // ACT //ASSERT
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Excercise <see cref="ArgumentNullException"/> because of null passed to <see cref="Enumerable.Count{TSource}(System.Collections.Generic.IEnumerable{TSource})"/>
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void UnfortunateCarelessUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Demonstrates the client code flow relying on returning null for empty collection.
        /// Exception is due to <see cref="Enumerable.First{TSource}(System.Collections.Generic.IEnumerable{TSource})"/> on an empty collection.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void UnfortunateEducatedUseOfCarelessMethod()
        {
            container.Populate();
            var elements = container.GetElementsInconvenientCareless();
            if (elements == null)
            {
                Assert.Inconclusive();
            }
            Assert.IsNotNull(elements.First());
        }

        /// <summary>
        /// Demonstrates the client code is bloated a bit, to compensate for implementation 'cleverness'.
        /// We can throw away the nullness result, because we don't know if the operation succeeded or not anyway.
        /// We are unfortunate to create a new instance of an empty collection.
        /// We might have already had one inside the implementation,
        /// but it have been discarded then in an effort to return null for empty collection.
        /// </summary>
        [Test]
        public void EducatedUseOfCarefullMethod()
        {
            Assert.AreEqual(0, (container.GetElementsInconvenientCarefull() ?? Enumerable.Empty<Element>()).Count());
        }
    }
}

2

Buna milyar dolarlık hatam diyorum… O zamanlar, nesne yönelimli bir dilde referanslar için ilk kapsamlı yazım sistemini tasarlıyordum. Amacım, derleyici tarafından otomatik olarak yapılan kontrol ile tüm referans kullanımlarının kesinlikle güvenli olmasını sağlamaktı. Ama null referansı koyma cazibesine karşı koyamadım, çünkü uygulanması çok kolaydı. Bu, son kırk yılda muhtemelen milyar dolarlık ağrı ve hasara neden olan sayısız hataya, güvenlik açığına ve sistem çökmesine neden oldu. - Tony Hoare, ALGOL W. mucidi.

Genel olarak ayrıntılı bir bok fırtınası için buraya bakın null. undefinedBaşka bir ifadeye katılmıyorum null, ama yine de okumaya değer. Ve nullsadece sorduğunuz durumda neden kaçınmanız gerektiğini açıklıyor . Özü, nullherhangi bir dilde özel bir durumdur. nullBir istisna olarak düşünmek zorundasınız . undefinedbu şekilde farklıdır, tanımlanmamış davranışla ilgili kod çoğu durumda sadece bir hatadır. C ve diğer birçok dilde tanımlanmamış davranışlar vardır, ancak çoğunun dilde bunun için bir tanımlayıcısı yoktur.


1

Birincil yazılım mühendisliği hedefi olan karmaşıklığı yönetme perspektifinden , bir API istemcisine gereksiz siklomatik karmaşıklık yaymaktan kaçınmak istiyoruz . İstemciye null değeri döndürmek, başka bir kod dalının döngüsel karmaşıklık maliyetini döndürmek gibidir.

(Bu, birim test yüküne karşılık gelir. Boş toplama dönüş durumuna ek olarak, boş dönüş durumu için bir test yazmanız gerekir.)

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.