IEnumerable के संभावित कई गणन के लिए चेतावनी संभालना


358

मेरे कोड में IEnumerable<>कई बार उपयोग करने की आवश्यकता होती है, इस प्रकार "संभावित एकाधिक गणना" की Resharper त्रुटि प्राप्त करें IEnumerable

नमूना कोड:

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null || !objects.Any())
        throw new ArgumentException();

    var firstObject = objects.First();
    var list = DoSomeThing(firstObject);        
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);

    return list;
}
  • मैं होने के लिए objectsपैरामीटर को बदल सकता हूं Listऔर फिर संभावित कई गणना से बच सकता हूं लेकिन फिर मुझे उच्चतम वस्तु नहीं मिलती है जिसे मैं संभाल सकता हूं।
  • एक और बात यह है कि मैं क्या कर सकते हैं परिवर्तित करने के लिए है IEnumerableकरने के लिए Listविधि की शुरुआत में:

 public List<object> Foo(IEnumerable<object> objects)
 {
    var objectList = objects.ToList();
    // ...
 }

लेकिन यह सिर्फ अजीब है

इस परिदृश्य में आप क्या करेंगे?

जवाबों:


470

IEnumerableएक पैरामीटर के रूप में लेने में समस्या यह है कि यह कॉलर्स को "मैं इसे एनुमरेट करना चाहता हूं" बताता है। यह उन्हें नहीं बताता कि आप कितनी बार गणना करना चाहते हैं।

मैं ऑब्जेक्ट पैरामीटर को सूची में बदल सकता हूं और फिर संभावित एकाधिक गणना से बच सकता हूं लेकिन फिर मुझे उच्चतम वस्तु नहीं मिलती है जिसे मैं संभाल सकता हूं

उच्चतम वस्तु लेने का लक्ष्य महान है, लेकिन यह बहुत अधिक मान्यताओं के लिए जगह छोड़ देता है। क्या आप वास्तव में इस विधि के लिए किसी को LINQ to SQL क्वेरी पास करना चाहते हैं, केवल आपके लिए इसे दो बार एन्यूमरेट करना (हर बार संभावित अलग-अलग परिणाम प्राप्त करना?)

यहाँ गायब शब्दार्थ यह है कि एक कॉलर, जो शायद विधि के विवरण को पढ़ने के लिए समय नहीं लेता है, हो सकता है कि आप केवल एक बार पुनरावृति मान लें - इसलिए वे आपको एक महंगी वस्तु देते हैं। आपकी विधि हस्ताक्षर किसी भी तरह से संकेत नहीं करता है।

विधि हस्ताक्षर को IList/ में बदलने से ICollection, आप कम से कम फोन करने वाले को यह स्पष्ट कर देंगे कि आपकी अपेक्षाएं क्या हैं, और वे महंगी गलतियों से बच सकते हैं।

अन्यथा, अधिकांश डेवलपर्स विधि को देखकर आपको केवल एक बार पुनरावृति मान सकते हैं। यदि एक IEnumerableलेना इतना महत्वपूर्ण है, तो आपको .ToList()विधि की शुरुआत में करने पर विचार करना चाहिए ।

यह शर्म की बात है। .NET में एक इंटरफ़ेस नहीं है जो IEnumerable + Count + Indexer, Add / Remove आदि तरीकों के बिना है, जो कि मुझे संदेह है कि इस समस्या को हल करेगा।


31
क्या ReadOnlyCollection <T> आपकी इच्छित इंटरफ़ेस आवश्यकताओं को पूरा करता है? msdn.microsoft.com/en-us/library/ms132474.aspx
Dan

73
@DanNeely मैं सुझाव देता हूं कि IReadOnlyCollection(T)(.net 4.5 के साथ नया) सबसे अच्छा इंटरफ़ेस के रूप में इस विचार को व्यक्त करने के लिए कि यह एक ऐसा है IEnumerable(T)जिसे कई बार गणना करने का इरादा है। जैसा कि इस उत्तर में कहा गया है, IEnumerable(T)अपने आप में इतना सामान्य है कि यह अन-रिसेट करने योग्य सामग्री को भी संदर्भित कर सकता है, जो कि साइड इफेक्ट्स के बाद फिर से गणना नहीं की जा सकती है। लेकिन IReadOnlyCollection(T)इसका तात्पर्य पुन: पुनरावृत्ति से है।
बिंकी

1
यदि आप .ToList()विधि की शुरुआत में करते हैं, तो आपको पहले जांचना होगा कि क्या पैरामीटर अशक्त नहीं है। इसका मतलब है कि आपको दो ऐसे स्थान मिलेंगे जहाँ आप एक ArgumentException को फेंकेंगे: यदि पैरामीटर अशक्त है और यदि इसमें कोई आइटम नहीं है।
कॉमेकमे

मैंने इस लेख को IReadOnlyCollection<T>बनाम के शब्दार्थ पर पढ़ा IEnumerable<T>और फिर एक पैरामीटर के रूप में उपयोग करने के बारे में एक प्रश्न पोस्ट किया IReadOnlyCollection<T>, और किन मामलों में। मुझे लगता है कि मैं आखिरकार जिस निष्कर्ष पर पहुंचा हूं वह तर्क का पालन करना है। यह प्रयोग किया जाना चाहिए जहां गणना की उम्मीद है, जरूरी नहीं कि जहां कई गणना हो सकती है।
नव

32

यदि आपका डेटा हमेशा रिपीटेबल होने वाला है, तो शायद इसकी चिंता न करें। हालाँकि, आप इसे भी अनियंत्रित कर सकते हैं - यह विशेष रूप से उपयोगी है यदि आने वाला डेटा बड़ा हो सकता है (उदाहरण के लिए, डिस्क / नेटवर्क से पढ़ना):

if(objects == null) throw new ArgumentException();
using(var iter = objects.GetEnumerator()) {
    if(!iter.MoveNext()) throw new ArgumentException();

    var firstObject = iter.Current;
    var list = DoSomeThing(firstObject);  

    while(iter.MoveNext()) {
        list.Add(DoSomeThingElse(iter.Current));
    }
    return list;
}

नोट I ने DoSomethingElse के सिमेंटिक को थोड़ा बदल दिया है, लेकिन यह मुख्य रूप से अनियंत्रित उपयोग दिखाने के लिए है। उदाहरण के लिए, आप पुन: लपेट सकते हैं। आप इसे एक इट्रेटर ब्लॉक भी बना सकते हैं, जो अच्छा हो सकता है; फिर वहाँ नहीं है list- और आप yield returnआइटम प्राप्त करेंगे जैसे कि आप उन्हें प्राप्त करते हैं, बल्कि एक सूची में जोड़ने के लिए वापस किया जाएगा।


4
+1 खैर यह एक बहुत अच्छा कोड है, लेकिन मैंने कोड की शुद्धता और पठनीयता को ढीला कर दिया है।
gdoron

5
@gdoron सब कुछ सुंदर नहीं है; पी, मैं ऊपर नहीं, अपठनीय है, हालांकि। खासकर अगर यह एक इट्रेटर ब्लॉक में बना है - काफी प्यारा, आईएमओ।
मार्क Gravell

मैं बस लिख सकता हूं: "var ऑब्जेक्टलिस्ट = ऑब्जेक्ट्स। टॉलिस्ट ();" और सभी एन्युमरेटर हैंडलिंग को बचाएं।
gdoron

10
इस प्रश्न में @gdoron ने स्पष्ट रूप से कहा कि आप वास्तव में ऐसा नहीं करना चाहते हैं? पी इसके अलावा, अगर डेटा बहुत बड़ा (संभावित, अनंत - अनुक्रमों को परिमित करने की आवश्यकता नहीं है, तो एक टोललिस्ट () दृष्टिकोण सूट नहीं कर सकता है, लेकिन हो सकता है अभी भी iterated हो)। अंत में, मैं सिर्फ एक विकल्प प्रस्तुत कर रहा हूं। केवल आपको पूरा संदर्भ पता है कि यह वॉरंटेड है या नहीं। यदि आपका डेटा रिपीटेबल है, तो दूसरा वैध विकल्प है: कुछ भी मत बदलो! R # के बजाय उपेक्षा करें ... यह सब संदर्भ पर निर्भर करता है।
मार्क Gravell

1
मुझे लगता है कि मार्क का समाधान काफी अच्छा है। 1. यह बहुत स्पष्ट है कि 'सूची ’को कैसे आरंभ किया जाता है, यह बहुत स्पष्ट है कि सूची को कैसे पुनरावृत्त किया जाता है, और यह बहुत स्पष्ट है कि सूची के कौन से सदस्य संचालित हैं।
कीथ हॉफमैन

8

के बजाय IReadOnlyCollection<T>या IReadOnlyList<T>विधि हस्ताक्षर में उपयोग करना IEnumerable<T>, यह स्पष्ट करने का लाभ है कि आपको पुनरावृत्ति से पहले गिनती की जांच करने की आवश्यकता हो सकती है, या किसी अन्य कारण से कई बार पुनरावृति करने की आवश्यकता हो सकती है।

हालाँकि, उनके पास एक बड़ा नकारात्मक पहलू है जो समस्याओं का कारण बनेगा यदि आप अपने कोड को रीफ़्रैक्टर का उपयोग करने के लिए रीफ़्रैक्टर करने का प्रयास करते हैं, उदाहरण के लिए इसे गतिशील प्रॉक्सिंग के लिए अधिक परीक्षण योग्य और अनुकूल बनाने के लिए। मुख्य बिंदु यह है कि IList<T>इनसे विरासत में नहीं मिलता हैIReadOnlyList<T> , और इसी तरह अन्य संग्रह और उनके संबंधित केवल-पढ़ने के लिए इंटरफेस है। (संक्षेप में, यह इसलिए है क्योंकि .NET 4.5 पहले संस्करणों के साथ ABI संगतता रखना चाहता था। लेकिन उन्होंने .NET .NET में बदलने का अवसर नहीं लिया। )

इसका मतलब यह है कि यदि आप IList<T>कार्यक्रम के कुछ हिस्से से प्राप्त करते हैं और इसे दूसरे भाग में पास करना चाहते हैं जो एक उम्मीद करता है IReadOnlyList<T>, तो आप नहीं कर सकते हैं! आप हालांकि एक के IList<T>रूप में पारित कर सकते हैंIEnumerable<T>

अंत में, IEnumerable<T>सभी .NET इंटरफेस सहित सभी .NET संग्रह द्वारा समर्थित एकमात्र रीड-ओनली इंटरफ़ेस है। कोई अन्य विकल्प आपको काटने के लिए वापस आ जाएगा जैसा कि आप महसूस करते हैं कि आपने खुद को कुछ वास्तुकला विकल्पों से बाहर कर दिया है। इसलिए मुझे लगता है कि यह व्यक्त करने के लिए फ़ंक्शन हस्ताक्षरों में उपयोग करने के लिए उचित प्रकार है जिसे आप केवल पढ़ने के लिए संग्रह चाहते हैं।

(ध्यान दें कि आप हमेशा एक IReadOnlyList<T> ToReadOnly<T>(this IList<T> list)एक्सटेंशन मेथड लिख सकते हैं जो सरल कास्ट करता है यदि अंतर्निहित प्रकार दोनों इंटरफेस का समर्थन करता है, लेकिन आपको रिफैक्टिंग करते समय इसे हर जगह मैन्युअल रूप से जोड़ना होगा, जहां IEnumerable<T>हमेशा संगत होता है।)

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


2
+1। पुराने प्लेटफॉर्म पर आने के लिए और .NET प्लेटफ़ॉर्म (यानी IReadOnlyCollection <T>) द्वारा प्रदान की गई नई सुविधाओं के साथ इस मुद्दे को हल करने के लिए नए तरीकों पर प्रलेखन जोड़ने के लिए
केविन एविग्नन

4

यदि उद्देश्य वास्तव में मार्क ग्रेवेल द्वारा दिए गए उत्तर की तुलना में कई गणनाओं को रोकने के लिए है, तो पढ़ने के लिए एक ही है, लेकिन एक ही शब्दार्थ बनाए रखने से आप निरर्थक Anyऔर Firstकॉल को हटा सकते हैं और साथ जा सकते हैं:

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null)
        throw new ArgumentNullException("objects");

    var first = objects.FirstOrDefault();

    if (first == null)
        throw new ArgumentException(
            "Empty enumerable not supported.", 
            "objects");

    var list = DoSomeThing(first);  

    var secondList = DoSomeThingElse(objects);

    list.AddRange(secondList);

    return list;
}

ध्यान दें, यह मानता है कि आप IEnumerableसामान्य नहीं हैं या कम से कम एक संदर्भ प्रकार होने के लिए विवश हैं।


2
objectsअभी भी DoSomethingElse को पारित किया जा रहा है जो एक सूची वापस कर रहा है, इसलिए संभावना है कि आप अभी भी दो बार गणना कर रहे हैं। किसी भी तरह से अगर संग्रह SQL क्वेरी में एक LINQ था, तो आपने DB को दो बार मारा।
पॉल स्टोवेल

1
@PaulStovell, इसे पढ़ें: "यदि लक्ष्य वास्तव में मार्क
ग्रेवेल

यह कुछ अन्य स्टैकओवरफ्लो पोस्ट पर खाली सूचियों के अपवादों को फेंकने पर सुझाव दिया गया है और मैं इसे यहां सुझाऊंगा: खाली सूची पर अपवाद क्यों फेंका जाए? एक खाली सूची प्राप्त करें, एक खाली सूची दें। एक प्रतिमान के रूप में, यह बहुत सीधा है। यदि कॉलर को पता नहीं है कि वे एक खाली सूची से गुजर रहे हैं, तो यह समस्या है।
कीथ हॉफमैन

@ कीथ हॉफमैन, नमूना कोड ओपी द्वारा पोस्ट किए गए कोड के समान व्यवहार रखता है, जहां का उपयोग Firstखाली सूची के लिए अपवाद फेंक देगा। मुझे नहीं लगता कि यह कहना संभव है कि कभी भी खाली सूची पर अपवाद न फेंके या हमेशा खाली सूची पर अपवाद को फेंक दें। यह निर्भर करता है ...
जोआ एंजेलो

पर्याप्त जोआओ। आपके पोस्ट की आलोचना से अधिक भोजन के लिए विचार।
कीथ हॉफमैन

3

मैं आमतौर पर इस स्थिति में IEnumerable और IList के साथ अपने तरीके को अधिभारित करता हूं।

public static IEnumerable<T> Method<T>( this IList<T> source ){... }

public static IEnumerable<T> Method<T>( this IEnumerable<T> source )
{
    /*input checks on source parameter here*/
    return Method( source.ToList() );
}

मैं उन तरीकों की सारांश टिप्पणियों में समझाने का ध्यान रखता हूं, जिन्हें IEnumerable कहकर कॉल करना एक .ToList () करेगा।

प्रोग्रामर एक उच्च स्तर पर .ToList () का चयन कर सकता है यदि कई ऑपरेशन समाप्‍त किए जा रहे हैं और फिर IList अधिभार को कॉल करें या मेरे IEnumerable अधिभार का ध्‍यान रखें।


20
यह भयावह है।
माइक चैंबरलेन

1
@ माइकचैम्बरलेन हाँ, यह वास्तव में एक ही समय में भयानक और पूरी तरह से साफ है। दुर्भाग्य से कुछ भारी परिदृश्यों को इस दृष्टिकोण की आवश्यकता होती है।
मौरो सेम्पीट्रो

1
लेकिन फिर आप हर बार जब आप इसे IEnumerable parameratized विधि से पास करते हैं, तो आप इसे फिर से बना लेंगे। मैं इस संबंध में @MikeChamberlain से सहमत हूं, यह एक भयानक सुझाव है।
Krythic

2
@Krythic यदि आपको सूची को फिर से बनाने की आवश्यकता नहीं है, तो आप कहीं और एक प्रदर्शन करते हैं और IList अधिभार का उपयोग करते हैं। यही इस समाधान की बात है।
मौरो सेम्पीटरो

0

यदि आपको पूरे संग्रह की पुनरावृत्ति के बिना पहले तत्व को देखना हो, तो आपको इसकी आवश्यकता होगी:

public List<object> Foo(IEnumerable<object> objects)
{
    object firstObject;
    if (objects == null || !TryPeek(ref objects, out firstObject))
        throw new ArgumentException();

    var list = DoSomeThing(firstObject);
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);

    return list;
}

public static bool TryPeek<T>(ref IEnumerable<T> source, out T first)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    IEnumerator<T> enumerator = source.GetEnumerator();
    if (!enumerator.MoveNext())
    {
        first = default(T);
        source = Enumerable.Empty<T>();
        return false;
    }

    first = enumerator.Current;
    T firstElement = first;
    source = Iterate();
    return true;

    IEnumerable<T> Iterate()
    {
        yield return firstElement;
        using (enumerator)
        {
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current;
            }
        }
    }
}

-3

सबसे पहले, चेतावनी हमेशा इतना मतलब नहीं है। मैंने आमतौर पर यह सुनिश्चित करने के बाद इसे निष्क्रिय कर दिया कि यह प्रदर्शन बोतल गर्दन नहीं है। इसका मतलब सिर्फ यह है कि IEnumerableदो बार मूल्यांकन किया जाता है, जो आमतौर पर एक समस्या नहीं है जब तक कि evaluationखुद को लंबा समय न लगे। यहां तक ​​कि अगर यह एक लंबा समय लेता है, तो इस मामले में आपका एकमात्र तत्व पहली बार चारों ओर का उपयोग कर रहा है।

इस परिदृश्य में आप शक्तिशाली लाइनक एक्सटेंशन विधियों का और भी अधिक शोषण कर सकते हैं।

var firstObject = objects.First();
return DoSomeThing(firstObject).Concat(DoSomeThingElse(objects).ToList();

IEnumerableइस मामले में केवल एक बार कुछ परेशानी के साथ मूल्यांकन करना संभव है , लेकिन पहले प्रोफ़ाइल करें और देखें कि क्या यह वास्तव में समस्या है।


15
मैं IQueryable के साथ बहुत काम करता हूं और उन प्रकार की चेतावनियों को बंद करना बहुत खतरनाक है।
gdoron

5
दो बार मूल्यांकन करना मेरी किताबों में एक बुरी बात है। यदि विधि IEnumerable लेता है, तो मुझे इसके लिए LINQ to SQL क्वेरी पास करने का प्रलोभन दिया जा सकता है, जिसके परिणामस्वरूप कई DB कॉल होते हैं। चूंकि विधि हस्ताक्षर यह संकेत नहीं देता है कि यह दो बार गणना कर सकता है, इसलिए इसे या तो हस्ताक्षर को बदलना चाहिए (IList / ICollection को स्वीकार करना चाहिए) या केवल एक बार पुनरावृति करना चाहिए
पॉल स्टोवेल

4
जल्दी और बर्बाद करने की पठनीयता का अनुकूलन करना और इस तरह से अनुकूलन करने की संभावना है जो बाद में मेरी पुस्तक में एक बदतर तरीका है।
विद्दिस्ट्स

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

1
@vvd मैंने ऐसी किसी भी चीज की सिफारिश नहीं की। बेशक, आपको IEnumerablesहर बार इसे पुनरावृत्त करने के लिए अलग-अलग परिणाम देने की गणना नहीं करनी चाहिए या इसे पुनरावृत्त करने के लिए वास्तव में लंबा समय लेना चाहिए। मार्क ग्रेवेल्स का जवाब देखें - वह भी सबसे पहले और सबसे महत्वपूर्ण है "शायद चिंता न करें"। बात यह है - बहुत बार आप यह देखते हैं कि आपको चिंता करने के लिए कुछ भी नहीं मिला है।
vidstige
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.