c# - क्या शून्य या खाली संग्रह वापस करना बेहतर है?




collections (12)

यह एक सामान्य सवाल है (लेकिन मैं सी # का उपयोग कर रहा हूं), सबसे अच्छा तरीका क्या है (सर्वोत्तम अभ्यास), क्या आप एक ऐसे तरीके के लिए शून्य या खाली संग्रह वापस करते हैं जिसमें रिटर्न प्रकार के रूप में संग्रह होता है?


मैं इसे अपनी अरब डॉलर की गलती कहता हूं ... उस समय, मैं ऑब्जेक्ट उन्मुख भाषा में संदर्भों के लिए पहली व्यापक प्रकार प्रणाली तैयार कर रहा था। मेरा लक्ष्य यह सुनिश्चित करना था कि संकलक द्वारा स्वचालित रूप से निष्पादित जांच के साथ संदर्भों का सभी उपयोग बिल्कुल सुरक्षित होना चाहिए। लेकिन मैं एक संक्षिप्त संदर्भ में प्रलोभन का विरोध नहीं कर सका, क्योंकि यह लागू करना इतना आसान था। इसने असंख्य त्रुटियों, कमजोरियों और सिस्टम दुर्घटनाओं को जन्म दिया है, जो पिछले 40 वर्षों में शायद अरबों डॉलर के दर्द और क्षति का कारण बन गया है। - टोनी होरे, आल्गोल डब्ल्यू के आविष्कारक।

सामान्य रूप से null बारे में एक विस्तृत छल तूफान के लिए here देखें। मैं इस बयान से सहमत नहीं हूं कि undefined एक और null , लेकिन यह अभी भी पढ़ने लायक है। और यह बताता है, आपको केवल इतना ही नहीं चाहिए कि आपने जो मामला पूछा है। सार यह है कि किसी भी भाषा में null एक विशेष मामला है। आपको अपवाद के रूप में null बारे में सोचना होगा। undefined इस तरह से अलग है, अपरिभाषित व्यवहार से निपटने वाला कोड ज्यादातर मामलों में बस एक बग है। सी और अधिकांश अन्य भाषाओं में भी व्यवहार को अपरिभाषित किया गया है, लेकिन उनमें से अधिकतर भाषा में इसके लिए कोई पहचानकर्ता नहीं है।


अपने ग्राहकों के पक्ष में हमेशा सोचें (जो आपके एपीआई का उपयोग कर रहे हैं):

रिटर्निंग 'नल' अक्सर ग्राहकों के साथ समस्याएं बनाता है जो नल चेक को सही तरीके से संभाल नहीं पाते हैं, जो रनटाइम के दौरान एक नलपोइंटर एक्सेप्शन का कारण बनता है। मैंने ऐसे मामलों को देखा है जहां इस तरह के लापता नल-चेक ने प्राथमिकता उत्पादन समस्या को मजबूर कर दिया है (एक ग्राहक ने शून्य मूल्य पर foreach (...) का उपयोग किया था)। परीक्षण के दौरान समस्या नहीं हुई, क्योंकि संचालित डेटा थोड़ा अलग था।


एक अन्य बिंदु है जिसका अभी तक उल्लेख नहीं किया गया है। निम्नलिखित कोड पर विचार करें:

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

इस विधि को कॉल करते समय सी # भाषा एक खाली गणनाकर्ता लौटाएगी। इसलिए, भाषा डिजाइन (और, इस प्रकार, प्रोग्रामर अपेक्षाओं) के साथ संगत होने के लिए एक खाली संग्रह वापस किया जाना चाहिए।


एक सप्ताह पहले या उससे पहले काम पर विकास दल के बीच हमारी चर्चा हुई थी, और हम लगभग सर्वसम्मति से खाली संग्रह के लिए गए थे। एक व्यक्ति उपरोक्त निर्दिष्ट माइक के कारण के लिए शून्य वापस करना चाहता था।



खाली उपभोक्ता अनुकूल है।

एक खाली गणना करने की एक स्पष्ट विधि है:

Enumerable.Empty<Element>()

खाली संग्रह हमेशा।

यह बेकार है:

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

संग्रह या गणना करने पर वापस लौटने के लिए इसे कभी भी वापस करने का सबसे अच्छा अभ्यास माना जाता है। हमेशा खाली खाली / संग्रह वापस करें। यह उपरोक्त बकवास को रोकता है, और आपकी कार को सहकर्मियों और आपके वर्ग के उपयोगकर्ताओं द्वारा अंडा कर दिया जाता है।

गुणों के बारे में बात करते समय, हमेशा अपनी संपत्ति को एक बार सेट करें और इसे भूल जाएं

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

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

.NET 4.6.1 में, आप इसे बहुत सघन कर सकते हैं:

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

विधियों को वापस करने वाली विधियों के बारे में बात करते समय, आप आसानी से खाली के बजाय एक खाली गणना कर सकते हैं ...

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

Enumerable.Empty<T>() का उपयोग करके लौटने से अधिक कुशल के रूप में देखा जा सकता है, उदाहरण के लिए, एक नया खाली संग्रह या सरणी।


ज्यादातर मामलों में एक खाली संग्रह वापस करना बेहतर है।

इसका कारण कॉलर, निरंतर अनुबंध, और आसान कार्यान्वयन के कार्यान्वयन की सुविधा है।

यदि कोई परिणाम रिक्त परिणाम इंगित करने के लिए शून्य देता है, तो कॉलर को गणना के अलावा एक शून्य जांच एडाप्टर को कार्यान्वित करना होगा। इस कोड को फिर विभिन्न कॉलर्स में डुप्लिकेट किया जाता है, इसलिए इस एडाप्टर को विधि के अंदर क्यों न डालें ताकि इसे पुन: उपयोग किया जा सके।

IENumerable के लिए शून्य का वैध उपयोग अनुपस्थित परिणाम, या एक ऑपरेशन विफलता का संकेत हो सकता है, लेकिन इस मामले में अन्य तकनीकों पर विचार किया जाना चाहिए, जैसे अपवाद फेंकना।

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

namespace .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://.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());
        }
    }
}

मैं तर्क दूंगा कि null खाली संग्रह के समान नहीं है और आपको यह चुनना चाहिए कि आप कौन सा सबसे अच्छा प्रतिनिधित्व कर रहे हैं। ज्यादातर मामलों में null कुछ भी नहीं है (एसक्यूएल को छोड़कर)। एक खाली संग्रह कुछ खाली है, यद्यपि एक खाली कुछ।

यदि आपको एक या दूसरे को चुनना है, तो मैं कहूंगा कि आपको शून्य के बजाय खाली संग्रह की ओर रुख करना चाहिए। लेकिन ऐसे समय होते हैं जब एक खाली संग्रह एक समान मूल्य के समान नहीं होता है।


यदि एक खाली संग्रह अर्थपूर्ण रूप से समझ में आता है, तो मैं यही लौटना पसंद करता हूं। GetMessagesInMyInbox() लिए एक खाली संग्रह लौटने से "आपके वास्तव में आपके इनबॉक्स में कोई संदेश नहीं है", जबकि null वापस लौटने से यह संवाद करने के लिए उपयोगी हो सकता है कि अपर्याप्त डेटा यह कहने के लिए उपलब्ध है कि कौन सी सूची लौटाई जा सकती है।


हालात के उपर निर्भर। यदि यह एक विशेष मामला है, तो शून्य वापस लौटें। यदि फ़ंक्शन बस एक खाली संग्रह वापस करने के लिए होता है, तो जाहिर है कि यह लौट रहा है ठीक है। हालांकि, अमान्य पैरामीटर या अन्य कारणों के कारण एक विशेष मामले के रूप में एक खाली संग्रह को वापस करना एक अच्छा विचार नहीं है, क्योंकि यह एक विशेष मामला स्थिति मास्किंग कर रहा है।

असल में, इस मामले में मैं आमतौर पर यह सुनिश्चित करने के लिए अपवाद फेंकना पसंद करता हूं कि इसे वास्तव में अनदेखा नहीं किया गया है :)

यह कहकर कि यह कोड को और अधिक मजबूत बनाता है (खाली संग्रह लौटकर) क्योंकि उन्हें शून्य स्थिति को संभालने की ज़रूरत नहीं है, क्योंकि यह केवल एक समस्या को मास्क कर रहा है जिसे कॉलिंग कोड द्वारा संभाला जाना चाहिए।


फ्रेमवर्क डिजाइन दिशानिर्देश द्वितीय संस्करण से (पृष्ठ 256):

संग्रह गुणों से या शून्य लौटने वाले विधियों से शून्य मान वापस न करें। इसके बजाय एक खाली संग्रह या एक खाली सरणी लौटें।

नल लौटने के लाभों पर एक और दिलचस्प लेख यहां दिया गया है (मैं ब्रैड अब्राम के ब्लॉग पर कुछ ढूंढने की कोशिश कर रहा था, और वह लेख से जुड़ा हुआ था)।

संपादित करें- क्योंकि एरिक लिपर्ट ने अब मूल प्रश्न पर टिप्पणी की है, मैं भी blogs.msdn.com/ericlippert/archive/2009/05/14/… से blogs.msdn.com/ericlippert/archive/2009/05/14/… करना चाहता हूं।





collections