जब संग्रह शून्य है तो .NET फॉर्च्यूप लूप NullRefException को क्यों फेंकता है?


231

इसलिए मैं अक्सर इस स्थिति में चला जाता हूं ... जहां Do.Something(...)एक अशक्त संग्रह लौटाता है, जैसे:

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

फिर, मैं इस संग्रह का उपयोग करने की कोशिश करता हूं:

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

मैं बस उत्सुक हूँ, क्यों एक फोड़ा लूप एक अशक्त संग्रह पर काम नहीं कर सकता? यह मेरे लिए तर्कसंगत लगता है कि 0 पुनरावृत्तियों को एक शून्य संग्रह के साथ निष्पादित किया जाएगा ... इसके बजाय यह फेंकता है a NullReferenceException। किसी को पता है कि यह क्यों हो सकता है?

यह कष्टप्रद है क्योंकि मैं एपीआई के साथ काम कर रहा हूं जो कि वे वापस लौटने पर बिल्कुल स्पष्ट नहीं हैं, इसलिए मैं if (someCollection != null)हर जगह समाप्त होता हूं ...

संपादित करें:foreach उपयोग करने के लिए समझाने के लिए आप सभी का धन्यवाद GetEnumeratorऔर अगर पाने के लिए कोई गणना करने वाला नहीं है, तो फॉरेक्स विफल हो जाएगा। मुझे लगता है कि मैं पूछ रहा हूं कि एन्यूमरेटर को हथियाने से पहले भाषा / रनटाइम क्यों नहीं कर सकता है या नहीं करेगा। यह मुझे लगता है कि व्यवहार अभी भी अच्छी तरह से परिभाषित किया जाएगा।


1
किसी सरणी को एक संग्रह कहने के बारे में कुछ गलत लगता है। लेकिन शायद मैं सिर्फ पुराना स्कूल हूं।
२०:

हां, मैं सहमत हूं ... मुझे यकीन भी नहीं है कि इस कोड बेस रिटर्न में इतने सारे तरीके क्यों हैं x_x
पोलारिस878

4
मुझे लगता है कि एक ही तर्क से इसे C # में सभी कथनों के लिए अच्छी तरह से परिभाषित किया जाएगा जब कोई nullमान दिया जाए । क्या आप इसे केवल foreachछोरों या अन्य बयानों के लिए सुझा रहे हैं ?
केन

7
@ केन ... मैं सिर्फ फॉरचून लूप सोच रहा हूं, क्योंकि मेरे लिए यह प्रोग्रामर को स्पष्ट प्रतीत होता है कि संग्रह खाली या गैर-मौजूद नहीं होने से कुछ नहीं होगा
पोलारिस878

जवाबों:


251

खैर, संक्षिप्त जवाब है "क्योंकि यही वह तरीका है जिसे कंपाइलर डिजाइनरों ने डिजाइन किया है।" वास्तविक रूप से, हालांकि, आपके संग्रह की वस्तु अशक्त है, इसलिए संकलनकर्ता को संग्रह के माध्यम से एन्यूमरेटर को लूप करने का कोई रास्ता नहीं है।

यदि आपको वास्तव में ऐसा कुछ करने की आवश्यकता है, तो अशक्त सह-संचालक की कोशिश करें:

int[] array = null;

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

3
कृपया मेरी अज्ञानता का बहाना करें, लेकिन क्या यह कुशल है? क्या प्रत्येक पुनरावृत्ति पर इसकी तुलना नहीं होती है?
user919426 10:29

20
मैं ऐसा नहीं मानता। उत्पन्न आईएल को देखते हुए, लूप शून्य तुलना के बाद है।
Robaticus

10
पवित्र नेक्रो ... कभी-कभी आपको यह देखने के लिए आईएल को देखना होगा कि संकलक क्या कर रहा है अगर कोई दक्षता हिट है। User919426 ने पूछा था कि क्या उसने प्रत्येक पुनरावृत्ति के लिए जाँच की थी। हालांकि इसका उत्तर कुछ लोगों के लिए स्पष्ट हो सकता है, यह सभी के लिए स्पष्ट नहीं है, और संकेत प्रदान करना कि आईएल को देखकर आपको बताएगा कि संकलक क्या कर रहा है, भविष्य में लोगों को अपने लिए मछली बनाने में मदद करता है।
रोबोटिकस

2
@Robaticus (यहां तक ​​कि बाद में भी) क्यों आईएल को लगता है कि क्योंकि विनिर्देश ऐसा कहता है। सिंटैक्टिक शुगर (उर्फ फॉरच) का विस्तार "में" के दाईं ओर की अभिव्यक्ति का मूल्यांकन करना है और GetEnumeratorपरिणाम पर कॉल करना है
रुण एफएस

2
@RuneFS - बिल्कुल। विनिर्देश को समझना या आईएल को देखने का एक तरीका है "क्यों"। या मूल्यांकन करने के लिए कि क्या दो अलग सी # दृष्टिकोण एक ही आईएल को उबालते हैं। यह अनिवार्य रूप से, ऊपर से शिम्मी के लिए मेरी बात थी।
रोबोट

148

foreach लूप GetEnumeratorविधि कहता है ।
यदि संग्रह है null, तो यह विधि कॉल का परिणाम है NullReferenceException

nullसंग्रह वापस करने के लिए यह बुरा अभ्यास है ; आपके तरीकों को इसके बजाय एक खाली संग्रह लौटना चाहिए।


7
मैं मानता हूं, खाली संग्रह हमेशा लौटाए जाने चाहिए ... लेकिन मैंने ये तरीके नहीं लिखे :)
Polaris878

19
@Polaris, बचाव के लिए अशक्त coalescing ऑपरेटर! int[] returnArray = Do.Something() ?? new int[] {};
जेएसबी JS

2
या: ... ?? new int[0]
केन

3
+1 रिक्त के बजाय खाली संग्रह वापस करने की टिप की तरह। धन्यवाद।
गैलिलियो

1
मैं एक बुरे अभ्यास के बारे में असहमत हूं: देखें ⇒ यदि कोई फ़ंक्शन विफल हो जाता है तो वह या तो एक खाली संग्रह लौटा सकता है - यह निर्माणकर्ता, मेमोरी आवंटन और शायद कोड के एक गुच्छा को निष्पादित करने के लिए एक कॉल है। या तो आप बस «अशक्त» वापस आ सकते हैं → स्पष्ट रूप से लौटने के लिए केवल एक कोड है और एक बहुत ही संक्षिप्त कोड है जांच करने के लिए तर्क है «अशक्त»। यह सिर्फ एक प्रदर्शन है।
हाय-एंजेल

47

एक खाली संग्रह और एक संग्रह के लिए एक अशक्त संदर्भ के बीच एक बड़ा अंतर है।

जब आप उपयोग करते हैं foreach, आंतरिक रूप से, यह IEnumerable के GetEnumerator को कॉल कर रहा है () विधि । जब संदर्भ शून्य है, तो यह इस अपवाद को बढ़ाएगा।

हालाँकि, यह खाली है IEnumerableया करने के लिए पूरी तरह से मान्य है IEnumerable<T>। इस मामले में, किसी भी चीज़ के लिए foreach "iterate" नहीं करेगा (क्योंकि संग्रह खाली है), लेकिन यह भी फेंक नहीं देगा, क्योंकि यह पूरी तरह से वैध परिदृश्य है।


संपादित करें:

व्यक्तिगत रूप से, अगर आपको इसके आसपास काम करने की आवश्यकता है, तो मैं एक एक्सटेंशन विधि सुझाऊंगा:

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

तब आप कॉल कर सकते हैं:

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

3
हाँ, लेकिन क्यों एन्यूमरेटर प्राप्त करने से पहले एक अशक्त जांच नहीं करता है?
पोलारिस878

12
@ पोलारिस878: क्योंकि इसे कभी भी अशक्त संग्रह के साथ इस्तेमाल करने का इरादा नहीं था। यह, IMO, एक अच्छी बात है - क्योंकि एक शून्य संदर्भ और एक खाली संग्रह को अलग से व्यवहार किया जाना चाहिए। यदि आप इसके आसपास काम करना चाहते हैं, तो तरीके हैं .. मैं एक दूसरे विकल्प को दिखाने के लिए संपादित करूंगा ...
रीड कोपसी

1
@ Polaris878: मैं आपके प्रश्न को पुनः दर्ज करने का सुझाव दूंगा: "एन्यूमरेटर प्राप्त करने से पहले रनटाइम को एक शून्य जांच क्यों करना चाहिए?"
रीड कोपसे

मुझे लगता है मैं पूछ रहा हूँ "क्यों नहीं?" ऐसा लगता है कि व्यवहार अभी भी अच्छी तरह से परिभाषित किया जाएगा
Polaris878

2
@ Polaris878: मुझे लगता है, जिस तरह से मैं इसके बारे में सोचता हूं, संग्रह के लिए अशक्त लौटना एक त्रुटि है। जिस तरह से यह अब है, रनटाइम आपको इस मामले में एक सार्थक अपवाद देता है, लेकिन यदि आप इस व्यवहार को पसंद नहीं करते हैं तो (यानी: ऊपर) काम करना आसान है। यदि संकलक आपसे यह छिपाता है, तो आप रनटाइम पर त्रुटि की जाँच कर लेंगे, लेकिन "इसे बंद" करने का कोई तरीका नहीं होगा ...
रीड कोपसे

12

यह लंबे समय से उत्तर दिया जा रहा है, लेकिन मैंने इसे निम्न तरीके से करने की कोशिश की है, ताकि केवल शून्य पॉइंटर अपवाद से बचा जा सके और सी # नल चेक ऑपरेटर का उपयोग करने वाले किसी व्यक्ति के लिए उपयोगी हो सकता है?

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

@kjbartel ने आपको एक साल से अधिक समय तक (" stackoverflow.com/a/32134295/401246 " पर हराया )। ;) यह सबसे अच्छा समाधान है, क्योंकि यह नहीं है: ए) के प्रदर्शन में गिरावट शामिल है (यहां तक ​​कि जब नहीं null) एलसीडी के पूरे लूप को सामान्य करना Enumerable(जैसा कि उपयोग ??करना), बी) को प्रत्येक परियोजना में एक एक्सटेंशन विधि जोड़ने की आवश्यकता होती है, और ग) से बचने के लिए null IEnumerables (Pffft! Puh-LEAZE! SMH।) से बचने की आवश्यकता होती है ।
टॉम

10

इसके आसपास काम करने के लिए एक और विस्तार विधि:

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

कई तरीकों से उपभोग करें:

(1) एक विधि के साथ जो स्वीकार करता है T:

returnArray.ForEach(Console.WriteLine);

(2) एक अभिव्यक्ति के साथ:

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

(3) एक बहु गुमनाम विधि के साथ

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

सिर्फ 3 उदाहरण में एक बंद कोष्ठक याद आ रही है। अन्यथा, सुंदर कोड जिसे दिलचस्प तरीकों से आगे बढ़ाया जा सकता है (लूप के लिए, उलट, छलांग, आदि)। साझा करने के लिए धन्यवाद।
लारा

इस तरह के एक अद्भुत कोड के लिए धन्यवाद, लेकिन मैं पहले तरीकों को समझ नहीं पाया, क्यों आप कंसोल को लिखते हैं। पैरामीटर के रूप में राइटलाइन, हालांकि इसकी प्रिंटिंग एरे तत्वों को।लेकिन समझ में नहीं आया
अजय सिंह

@ अजयसिंह Console.WriteLineएक विधि का एक उदाहरण है जो एक तर्क (ए Action<T>) लेता है । आइटम 1, 2 और 3 .ForEachविस्तार विधि से गुजरने वाले कार्यों के उदाहरण दिखा रहे हैं ।
जय

@ kjbartel का उत्तर (" stackoverflow.com/a/32134295/401246 पर" सबसे अच्छा समाधान है, क्योंकि यह नहीं है :) a) प्रदर्शन में गिरावट को शामिल करता है (भले ही नहीं null) एलसीडी के लिए पूरे लूप को सामान्य करना Enumerable(जैसा कि उपयोग ??करना होगा) ), बी) को हर प्रोजेक्ट में एक एक्सटेंशन मेथड जोड़ने की आवश्यकता है, या c) null IEnumerableएस से बचने की आवश्यकता है (Pftft! Puh-LEAZE! SMH।) के साथ शुरू करने के लिए (cuz, nullN / A का मतलब है, जबकि खाली सूची का मतलब है, यह appl है। लेकिन वर्तमान में, अच्छी तरह से, खाली ; (यानी एक एम्पल के कमीशन हो सकते हैं जो गैर-बिक्री के लिए N / A है या बिक्री के लिए खाली है)।
टॉम

5

अपनी सहायता के लिए एक विस्तार विधि लिखें:

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

क्योंकि एक रिक्त संग्रह के रूप में एक शून्य संग्रह एक ही बात नहीं है। एक खाली संग्रह एक संग्रह वस्तु है जिसमें कोई तत्व नहीं है; अशक्त संग्रह एक शून्य वस्तु है।

यहाँ कुछ करने की कोशिश है: किसी भी प्रकार के दो संग्रह घोषित करें। एक को सामान्य रूप से प्रारंभ करें ताकि वह खाली हो, और दूसरे को मान निर्दिष्ट करें null। फिर दोनों संग्रह में एक ऑब्जेक्ट जोड़ने का प्रयास करें और देखें कि क्या होता है।


3

इसका दोष है Do.Something()। यहां सबसे अच्छा अभ्यास एक शून्य के बजाय आकार 0 (जो संभव है) की एक सरणी वापस करना होगा।


2

क्योंकि पर्दे के पीछे foreachएक एन्यूमरेटर प्राप्त होता है, इसके बराबर:

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

2
इसलिए? अगर यह पहले शून्य है और लूप को छोड़ दें तो यह केवल जाँच क्यों नहीं कर सकता है? AKA, वास्तव में विस्तार विधियों में क्या दिखाया गया है? सवाल यह है कि क्या लूप को अशक्त करने के लिए डिफॉल्ट करना बेहतर है, यदि अशक्त या अपवाद को फेंकना है? मुझे लगता है कि इसे छोड़ना बेहतर है! ऐसा प्रतीत होता है कि नल के कंटेनरों को लूप की बजाय छोड़ दिया जाना चाहिए क्योंकि लूप्स कुछ ऐसा करने के लिए होते हैं यदि कंटेनर नॉन-नल है।
सार

@AbstractDissonance आप सभी nullसंदर्भों के साथ एक ही तर्क कर सकते हैं , उदाहरण के लिए जब सदस्यों तक पहुँचने। आमतौर पर यह एक त्रुटि है, और यदि ऐसा नहीं है, तो उदाहरण के लिए विस्तार विधि के साथ इसे संभालना काफी सरल है, जिसे किसी अन्य उपयोगकर्ता ने उत्तर के रूप में प्रदान किया है।
लुसेरो

1
मुझे ऐसा नहीं लगता। फॉरेक्स का मतलब संग्रह से अधिक काम करना है और यह सीधे एक अशक्त वस्तु को संदर्भित करने से अलग है। जबकि कोई एक ही तर्क दे सकता है, मैं शर्त लगाता हूं कि अगर आपने दुनिया के सभी कोड का विश्लेषण किया, तो आपके पास सबसे आगे की लूप्स होंगी, जिनके सामने केवल "शून्य" होने पर लूप को बायपास करने के लिए उनके सामने किसी प्रकार की अशक्त जांच होगी (जो कि इसलिए खाली जैसा ही व्यवहार किया जाता है)। मुझे नहीं लगता कि कोई भी अशक्त संग्रह पर लूपिंग करने के बारे में सोचता है क्योंकि वे चाहते हैं कि कोई चीज लूप को अनदेखा कर दे और अगर कलेक्शन शून्य है तो केवल लूप को नजरअंदाज कर देगा। हो सकता है, बल्कि, एक foreach? (C में var x) का उपयोग किया जा सके।
AbstractDissonance

मैं जो मुख्य रूप से बनाने की कोशिश कर रहा हूं वह यह है कि यह कोड में थोड़ा सा कूड़े बनाता है क्योंकि किसी अच्छे कारण के लिए हर बार जांच करनी पड़ती है। विस्तार, बेशक, काम करते हैं लेकिन एक भाषा सुविधा को जोड़ा जा सकता है ताकि इन मुद्दों को बहुत अधिक समस्या के बिना से बचा जा सके। (मुख्य रूप से मुझे लगता है कि वर्तमान विधि छिपी हुई बग पैदा करती है क्योंकि प्रोग्रामर चेक लगाना भूल सकता है और इसलिए एक अपवाद है ... क्योंकि या तो वह उम्मीद करता है कि चेक लूप से पहले कहीं और हो सकता है या यह सोच रहा है कि यह पहले से आरंभिक था (जो यह बदल गया है या हो सकता है)। लेकिन किसी भी कारण से, व्यवहार वैसा ही होगा जैसे कि खाली हो।
AbstractDissonance

@AbstractDissonance खैर, कुछ उचित स्थैतिक विश्लेषण के साथ आप जानते हैं कि आपके पास नल कहां हो सकते हैं और कहां नहीं। यदि आपको एक अशक्तता मिलती है, जहाँ आप उम्मीद नहीं करते हैं कि चुपचाप समस्याओं को अनदेखा करने के बजाय आईएमएचओ ( तेजी से विफल होने की भावना ) में विफल होना बेहतर है । इसलिए मुझे लगता है कि यह सही व्यवहार है।
लुसेरो

1

मुझे लगता है कि अपवाद क्यों दिया गया है इसका स्पष्टीकरण यहां दिए गए उत्तरों से बहुत स्पष्ट है। मैं बस जिस तरह से मैं आम तौर पर संग्रह के साथ काम करने के साथ पूरक करना चाहता हूं। क्योंकि, कुछ समय, मैं संग्रह का अधिक उपयोग करता हूं और एक बार परीक्षण करना पड़ता है यदि हर बार अशक्त। उससे बचने के लिए, मैं निम्नलिखित कार्य करता हूं:

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

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

इस तरह हम बिना किसी डर के अपवाद के रूप में संग्रह का उपयोग कर सकते हैं और हम अत्यधिक सशर्त बयानों के साथ कोड को विचलित नहीं करते हैं।

नल चेक ऑपरेटर ?.का उपयोग करना भी एक महान दृष्टिकोण है। लेकिन, सरणियों के मामले में (प्रश्न में उदाहरण की तरह), इसे पहले सूची में तब्दील किया जाना चाहिए:

    int[] returnArray = DoSomething();

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

2
एक ForEachविधि में प्रवेश करने के लिए एक सूची में बदलना उन चीजों में से एक है जो मुझे एक कोडबेस में नफरत है।
huysentruitw

मैं सहमत हूं ... मैं जितना संभव हो उतना बचता हूं। :(
एलियल्सन पिफ़र

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.