Soru Toplama null olduğunda, neden NET foreach döngüsü NullRefException atar?


Bu yüzden sık sık bu durumla karşılaşıyorum ... Do.Something(...) boş bir koleksiyon döndürür, şöyle ki:

int[] returnArray = Do.Something(...);

Ardından, bu koleksiyonu şu şekilde kullanmaya çalışıyorum:

foreach (int i in returnArray)
{
    // do some more stuff
}

Sadece merak ediyorum, neden bir foreach döngüsü null koleksiyonunda çalışamaz? 0 yinelemenin boş bir koleksiyonla çalıştırılacağı bana mantıklı geliyor. NullReferenceException. Bunun neden olabileceğini bilen var mı?

Bu, tam olarak ne geri döndüğüne dair net olmayan API'lerle çalışırken can sıkıcı bir durumdur. if (someCollection != null) her yerde...

Düzenle: Bunu açıkladığın için hepinize teşekkür ederim foreach kullanımları GetEnumerator ve eğer yapılacak bir sayım yok ise, foreach başarısız olur. Sanırım dil / çalışma zamanı niçin numaralandırıcıyı yakalamadan önce null bir çek yapamayacağımı veya yapmayacağımı soruyorum. Bana göre davranış hala iyi tanımlanmış olurdu.


169
2018-06-21 20:09


Menşei


Bir dizi bir dizi bir koleksiyon çağırmak konusunda yanlış geliyor. Ama belki sadece eski okulum. - Robaticus
Evet, katılıyorum ... Bu kod tabanı için neden bu kadar çok yöntemin neden x_x dizildiğini bile bilmiyorum - Polaris878
Aynı mantıkla, iyi tanımlanmış olacağını düşünüyorum. herşey verildiğinde C # ifadeleri null değer. Bunu sadece bunun için öneriyor musunuz? foreach döngüler veya diğer ifadeler? - Ken
@ Ken ... Sadece foreach döngülerimi düşünüyorum, çünkü eğer program boş veya var olmuyorsa programcıya hiçbir şey olamayacağı anlaşılıyor. - Polaris878
Benzer stackoverflow.com/q/6455311/80161 ve stackoverflow.com/a/11734449/80161 - Nathan Hartley


Cevaplar:


Pekala, kısa cevap "çünkü derleyici tasarımcıların tasarladığı yol budur." Gerçekçi olsa da, koleksiyon nesneniz boştur, bu nedenle derleyicinin koleksiyonlayıcıyı döngü içinde toplaması için bir yol yoktur.

Gerçekten böyle bir şey yapmanız gerekiyorsa, boş birleştirme işlecini deneyin:

    int[] array = null;

    foreach (int i in array ?? Enumerable.Empty<int>())
    {
        System.Console.WriteLine(string.Format("{0}", i));
    }

181
2018-06-21 20:15



Lütfen cehaletimi bağışlayın, ama bu verimli mi? Her yinelemede bir karşılaştırma yapılmaz mı? - user919426
Ben inanmıyorum. Oluşturulan IL'ye bakıldığında, döngü null karşılaştırmasından sonradır. - Robaticus
Kutsal necro ... Bazen herhangi bir verimlilik isabeti olup olmadığını anlamak için derleyicinin ne yaptığını görmek için IL'ye bakmak zorundasınız. User919426, her yineleme için kontrol yapıp yapmadığını sormuştu. Cevap, bazı insanlar için açık olsa da, herkes için açık değildir ve IL'ye bakmanın derleyicinin ne yaptığını anlatacağına dair ipucu sağlamak, insanların gelecekte kendileri için balık tutmasına yardımcı olur. - Robaticus
@Robaticus (daha sonra neden olsa bile) IL, neden şartname öyle diyor ki. Sözdizimsel şekerin genişletilmesi (foreach), “in” in sağ tarafındaki ifadeyi değerlendirmek ve çağırmaktır. GetEnumerator sonuçta - Rune FS
@RuneFS - tam olarak. Spesifikasyonu anlamak veya IL'ye bakmak, "neden" olduğunu anlamanın bir yoludur. Ya da iki farklı C # yaklaşımının aynı IL'ye kaynadığını değerlendirmek için. Bu, esasen, yukarıda Shimmy için benim nokta oldu. - Robaticus


bir foreach döngü çağırır GetEnumerator yöntem.
Koleksiyon ise null, bu yöntem arama sonuçları NullReferenceException.

Bir dönmek için kötü bir uygulamadır null Toplamak; yöntemleriniz bunun yerine boş bir koleksiyon vermelidir.


124
2018-06-21 20:11



Katılıyorum, boş koleksiyonlar her zaman iade edilmelidir ... ancak bu yöntemleri yazmadım :) - Polaris878
@Polaris, kurtarma için null coletcing operatörü! int[] returnArray = Do.Something() ?? new int[] {}; - JSBձոգչ
lol Bu çifte soru işaretlerini seviyorum - Polaris878
Veya: ... ?? new int[0]. - Ken
+1 Boş yerine boş koleksiyonları döndürmenin ucu gibi. Teşekkürler. - Galilyou


Boş bir koleksiyon ile bir koleksiyona boş başvuru arasında büyük bir fark var.

Kullandığında foreachdahili olarak, bu IEnumerable'ın çağırıyor GetEnumerator() yöntem. Referans null olduğunda, bu istisnayı artıracaktır.

Ancak, boş olması için mükemmel bir şekilde geçerlidir. IEnumerable veya IEnumerable<T>. Bu durumda, foreach hiçbir şey üzerinde “yineleme” yapmayacaktır (koleksiyon boş olduğundan), ama aynı zamanda mükemmel bir senaryo olduğu için atmayacaktır.


Düzenle:

Kişisel olarak, bu konuda çalışmanız gerekirse, bir uzantı yöntemi öneririm:

public static IEnumerable<T> AsNotNull<T>(this IEnumerable<T> original)
{
     return original ?? Enumerable.Empty<T>();
}

Sonra sadece arayabilirsin:

foreach (int i in returnArray.AsNotNull())
{
    // do some more stuff
}

40
2018-06-21 20:12



Evet, ancak NEDEN, denetleyiciyi almadan önce niçin bir çek denetimi yapmıyor? - Polaris878
@ Polaris878: Çünkü asla boş bir koleksiyonla kullanılmayacaktı. Bu IMO, iyi bir şeydir - boş bir başvuru ve boş bir koleksiyon ayrı ayrı ele alınmalıdır. Bu konuda çalışmak istiyorsanız, yolları var .. .Başka bir seçenek göstermek için düzenleyeceğim ... - Reed Copsey
@ Polaris878: Sorunuzu yanıtlamanızı öneririm: "Niçin çalışma zamanını numaralandırıcıyı almadan önce null bir kontrol yapmalı?" - Reed Copsey
Sanırım "neden olmasın?" Diye soruyorum. lol davranış hala iyi tanımlanmış gibi görünüyor - Polaris878
@ Polaris878: Sanırım, onu düşünürüm, bir koleksiyon için null döndürme bir hatadır. Şimdiki haliyle, çalışma zamanı bu durumda size anlamlı bir istisna verir, ancak bu davranışı sevmediğinizde (örneğin: yukarıda) çalışmak kolaydır. Derleyici bunu sizden sakladıysa, çalışma zamanında hata denetimini kaybedersiniz, ancak "kapatmak" için bir yol olmaz ... - Reed Copsey


Bu etrafında çalışmak için başka bir uzantısı yöntemi:

public static void ForEach<T>(this IEnumerable<T> items, Action<T> action)
{
    if(items == null) return;
    foreach (var item in items) action(item);
}

Çeşitli yollarla tüketin:

(1) kabul eden bir yöntemle T:

returnArray.ForEach(Console.WriteLine);

(2) bir ifadeyle:

returnArray.ForEach(i => UpdateStatus(string.Format("{0}% complete", i)));

(3) çok satırlı anonim yöntemle

int toCompare = 10;
returnArray.ForEach(i =>
{
    var thisInt = i;
    var next = i++;
    if(next > 10) Console.WriteLine("Match: {0}", i);
});

7
2018-06-21 20:33



Üçüncü örnekte sadece kapanış parantezini kaçırdım. Aksi takdirde, ilginç yollarla daha da genişletilebilecek güzel kodlar (döngüler, geri dönüşler, sıçrayanlar vb.). Paylaşım için teşekkürler. - Lara
Böyle harika bir kod için teşekkürler, Ama ilk metodları anlamadım, neden config.writeline'ı parametre olarak geçirirsiniz, bununla birlikte array elements.but'u anlayamadım. - Ajay Singh
@AjaySingh Console.WriteLine sadece bir argüman alan bir yöntemin örneğidir (bir Action<T>). 1, 2 ve 3 numaralı öğeler, .ForEach uzatma yöntemi. - Jay


Sadece size yardımcı olacak bir uzantı yöntemi yazın:

public static class Extensions
{
   public static void ForEachWithNull<T>(this IEnumerable<T> source, Action<T> action)
   {
      if(source == null)
      {
         return;
      }

      foreach(var item in source)
      {
         action(item);
      }
   }
}

5
2018-06-21 20:12





Çünkü boş bir koleksiyon boş bir koleksiyonla aynı şey değildir. Boş koleksiyon hiçbir öğeye sahip olmayan bir koleksiyon nesnesidir; Boş bir koleksiyon varolmayan bir nesnedir.

İşte denenecek bir şey var: Her türden iki koleksiyon açıkla. Birini normal olarak sıfırlayın, böylece boş ve diğer değeri ata null. Daha sonra her iki koleksiyona da bir nesne eklemeyi deneyin ve ne olduğunu görün.


5
2018-06-21 20:13





Bu cevap uzun bir süredir devam ediyor, ancak bunu sadece null pointer istisnasından kaçınmak için yapmayı denedim ve C # null kontrol operatörünü kullanan biri için yararlı olabilir.

     //fragments is a list which can be null
     fragments?.ForEach((obj) =>
        {
            //do something with obj
        });

4
2017-11-11 17:27





Bu bir hata Do.Something(). Buradaki en iyi uygulama, sıfır yerine, 0 (mümkün olan) bir dizi döndürmek olacaktır.


3
2018-06-21 20:13





Çünkü sahnelerin arkasında foreach bir sayım alır, buna eşdeğerdir:

using (IEnumerator<int> enumerator = returnArray.getEnumerator()) {
    while (enumerator.MoveNext()) {
        int i = enumerator.Current;
        // do some more stuff
    }
}

2
2018-06-21 20:12



yani? Neden sadece ilk boş olup olmadığını kontrol edemiyor ve döngüyü atlayamıyor? AKA, uzantı yöntemlerinde tam olarak ne var? Soru şu ki, null yoksa veya bir istisna atmak için döngüyü atlamak varsayılan mıdır? Sanırım atlamak daha iyi! İlmeklerin bir şey yapması gerektiği için boş kapların döngüden ziyade atlanması anlamına geldiği düşünülür. EĞER kapsayıcı boş değil. - AbstractDissonance
@AbstractDissonance Hepinizle aynı şeyi tartışabilirsiniz null referanslar, ör. üyelere erişirken. Tipik olarak bu bir hatadır ve eğer böyle değilse, örneğin, başka bir kullanıcının cevap olarak sağladığı uzantı yöntemiyle bunu halledecek kadar basittir. - Lucero
Ben öyle düşünmüyorum. Foreach, koleksiyon üzerinde çalışacak ve doğrudan boş bir nesneyi göndermekten farklıdır. Biri aynı şeyi tartışabilirken, bahse girerim ki eğer dünyadaki tüm kodları analiz ettiyseniz, en çok kullanılan forma döngülerinin, koleksiyonun "null" olduğu zamanlar döngüyü atlamak için önlerinde bir tür boş kontrol var. bu nedenle boş olarak aynı muameleye tabi tutuldu. Kimsenin null bir koleksiyon üzerinde olmasını istediği bir şey olarak düşünmeyi düşünür ve koleksiyonun boş olması durumunda döngüyü göz ardı etmeyi tercih eder. Belki, daha doğrusu, bir foreach (C cinsinden var) kullanılabilir. - AbstractDissonance
Temelde yapmaya çalıştığım nokta, kodun her zaman iyi bir sebepten dolayı kontrol edilmesi gerektiğinden, kodda biraz çöp yaratmasıdır. Elbette, bu tür şeyleri çok fazla sorun olmaktan kaçınmak için elbette bir dil özelliği değil, bir dil özelliği eklenebilir. (çoğunlukla bence programcı kontrolü ve dolayısıyla bir istisna koymayı unutabildiği için gizli metotlar gizli hatalar üretiyor ... çünkü ya kontrolün döngüsünden önce başka bir yerde gerçekleşmesini beklemiyor ya da önceden başlatılmış olduğunu düşünüyor. Her iki nedenden ötürü, davranış, eğer boşsa aynı olurdu. - AbstractDissonance
@AbstractDissonance Eh, bazı uygun statik analizlerle null'ların nerede olabileceğini ve nerede bulunamayacağını biliyorsunuz. Eğer beklemediğiniz bir boş alan elde ederseniz, IMHO'nun sessizce göz ardı edilmesi yerine başarısız olmak daha iyidir. hızlı başarısız). Bu yüzden bunun doğru davranış olduğunu düşünüyorum. - Lucero


İstisnaın neden atıldığının açıklaması burada verilen cevaplarla çok açık. Sadece bu koleksiyonlarla çalıştığım şekilde tamamlamayı diliyorum. Çünkü, bazı zamanlarda, koleksiyonu bir kereden fazla kullanıyorum ve her seferinde boşsa test etmeliyim. Bunu önlemek için aşağıdakileri yaparım:

    var returnArray = DoSomething() ?? Enumerable.Empty<int>();

    foreach (int i in returnArray)
    {
        // do some more stuff
    }

Bu şekilde koleksiyonu istisnasız korkmadan istediğimiz kadar kullanabiliriz ve koda aşırı koşullu ifadelerle bakmayız.

Boş çek operatörünü kullanma ?. aynı zamanda harika bir yaklaşım. Ancak, diziler durumunda (sorudaki örnek gibi), daha önce Listeye dönüştürülmelidir:

    int[] returnArray = DoSomething();

    returnArray?.ToList().ForEach((i) =>
    {
        // do some more stuff
    });

0
2018-06-28 17:27



Sadece erişim için bir listeye dönüştürme ForEach Yöntem, bir kod tabanında nefret ettiğim şeylerden biridir. - Housy
Katılıyorum ... Bunu olabildiğince engellemek istiyorum. :( - Alielson Piffer


SPListItem item;
DataRow dr = datatable.NewRow();

dr["ID"] = (!Object.Equals(item["ID"], null)) ? item["ID"].ToString() : string.Empty;

-1
2018-04-02 12:44