2013-11-15 12 views
14

मैंने अभी भी Skip/Take .NET Framework (IEnumerable<T> प्रकार) के विस्तार विधियों के स्रोत कोड पर एक नज़र डाली और पाया कि आंतरिक कार्यान्वयन के साथ काम कर रहा है GetEnumerator विधि:छोड़ने का प्रदर्शन (और इसी तरह के कार्यों, जैसे टेक)

// .NET framework 
    public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count) 
    { 
     if (source == null) throw Error.ArgumentNull("source"); 
     return SkipIterator<TSource>(source, count); 
    } 

    static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count) 
    { 
     using (IEnumerator<TSource> e = source.GetEnumerator()) 
     { 
      while (count > 0 && e.MoveNext()) count--; 
      if (count <= 0) 
      { 
       while (e.MoveNext()) yield return e.Current; 
      } 
     } 
    } 

मान लीजिए कि मैं एक IEnumerable<T> 1000 तत्वों (अंतर्निहित प्रकार List<T> है) है। अगर मैं सूची कर रहा हूं तो क्या होगा। स्किप (9 0 9)। टेक (10)? क्या यह अंतिम दस लेने से पहले 9 0 9 के पहले तत्वों को थकाऊ करेगा? (इस तरह मैं इसे समझता हूं)। यदि हाँ, तो मुझे समझ नहीं आता क्यों माइक्रोसॉफ्ट Skip विधि इस तरह लागू नहीं किया:

// Not tested... just to show the idea 
    public static IEnumerable<T> Skip<T>(this IEnumerable<T> source, int count) 
    { 
     if (source is IList<T>) 
     { 
      IList<T> list = (IList<T>)source; 
      for (int i = count; i < list.Count; i++) 
      { 
       yield return list[i]; 
      } 
     } 
     else if (source is IList) 
     { 
      IList list = (IList)source; 
      for (int i = count; i < list.Count; i++) 
      { 
       yield return (T)list[i]; 
      } 
     } 
     else 
     { 
      // .NET framework 
      using (IEnumerator<T> e = source.GetEnumerator()) 
      { 
       while (count > 0 && e.MoveNext()) count--; 
       if (count <= 0) 
       { 
        while (e.MoveNext()) yield return e.Current; 
       } 
      } 
     } 
    } 

वास्तव में, वे किया था कि उदाहरण के लिए Count विधि ... के लिए

// .NET Framework... 
    public static int Count<TSource>(this IEnumerable<TSource> source) 
    { 
     if (source == null) throw Error.ArgumentNull("source"); 

     ICollection<TSource> collectionoft = source as ICollection<TSource>; 
     if (collectionoft != null) return collectionoft.Count; 

     ICollection collection = source as ICollection; 
     if (collection != null) return collection.Count; 

     int count = 0; 
     using (IEnumerator<TSource> e = source.GetEnumerator()) 
     { 
      checked 
      { 
       while (e.MoveNext()) count++; 
      } 
     } 
     return count; 
    } 

तो क्या कारण है?

+1

मुझे पता चला है कि यह मानना ​​हमेशा सर्वोत्तम होता है कि उन विधियों को कभी अनुकूलित नहीं किया जाता है।यहां तक ​​कि गणना() के लिए, यह 'ICollection <>' के लिए अनुकूलित करता है, लेकिन 'IReadOnlyCollection <>' नहीं। यदि आपको इसे अनुकूलित करने की आवश्यकता है, तो अपना खुद का लिखें। – RobSiklos

+0

क्योंकि उन्होंने उस अनुकूलन को जोड़ने के लिए कभी भी परेशान नहीं किया? अगर आपको लगता है कि इससे मदद मिलती है तो मुझे आपके साथ ऐसा कोई समस्या नहीं दिखती है। लेकिन ध्यान दें कि फिर 'myList.Select (..)। छोड़ें (100) 'myList.Skip (100) से धीमा है। चयन करें (..)', भले ही वे कार्यात्मक रूप से वही हों। –

+0

यह भी ध्यान दें कि लिंक-टू-एसक्यूएल और ईएफ 'छोड़ें' और 'टेक' में एसक्यूएल क्वेरी में धक्का दिया जाता है, इसलिए यह पूर्व वस्तुओं के माध्यम से पुनरावृत्ति नहीं करता है। (_SQL_ एक टेबल/इंडेक्स स्कैन के माध्यम से हो सकता है, लेकिन लिंक नहीं करता है) –

उत्तर

10

जॉन स्कीट के उत्कृष्ट ट्यूटोरियल फिर से लागू करने Linq, वह चर्चा (संक्षेप में) में है कि बहुत सवाल:

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

यह अनुकूलन पर रोक लगाने के उचित कारण की तरह लगता है, लेकिन मैं मानता हूं कि विशिष्ट मामलों के लिए, यह अनुकूलन करने के लिए उपयुक्त हो सकता है यदि आप गारंटी दे सकते हैं कि आपका स्रोत संशोधित नहीं किया जा सकता/नहीं ।

+0

ठीक है, मैं बिंदु देखता हूं ... लेकिन वे इस मामले में 'IReadOnlyList ' का उपयोग कर सकते थे ... मुझे लगता है कि यह इंटरफ़ेस पर्याप्त रूप से उपयोग नहीं किया जाता है (और इस प्रकार परीक्षण की लागत 'IEnumerable ' है 'IReadOnlyList ' बहुत अधिक है)? – Bidou

+0

@ बिडौ, यही वह है जो मुझे लगता है। एक सर्वव्यापी 'छोड़ें)) कार्यान्वयन के लिए, उस छोटे-छोटे इंटरफ़ेस की जांच करना शायद इसके लायक नहीं समझा जा सकता है। –

+1

कार्यात्मक उद्देश्यों के लिए, IList मूल रूप से एक कैश संग्रह है। गणनाकर्ता पुनरावृत्तियों के बीच एक आईएलआईस्ट को संशोधित करना आम तौर पर अपवाद उठाता है, इसलिए मुझे नहीं लगता कि ऑप्टिमाइज़ेशन क्यों नहीं किया जाना चाहिए। वास्तव में, यदि आप सूची को संशोधित करने वाले यादृच्छिक समानांतर दुष्प्रभावों की संभावना को स्वीकार करते हैं, तो आपके परिणाम यादृच्छिक होंगे चाहे आप सीधे छोड़ दें या नहीं। – glopes

1

मुझे लगता है कि वे InvalidOperationException फेंकना चाहते थे "संग्रह संशोधित किया गया था ..." जब अंतर्निहित संग्रह को दूसरे थ्रेड में संशोधित किया जाता है। आपका संस्करण ऐसा नहीं करता है। यह भयानक परिणाम मिलेगा।

यह मानक अभ्यास है एमएसएफटी सभी संग्रहों में नेट फ्रेमवर्क का पालन कर रहा है जो थ्रेड सुरक्षित नहीं है (कुछ असाधारण हैं)।

2

जैसा कि लेडबटर ने उल्लेख किया था, जब जॉन स्कीट reimplemented LINQ, उन्होंने उल्लेख किया कि आपके Skip जैसे अनुकूलन "उस स्थिति को नहीं देख पाएंगे जहां स्रोत पुनरावृत्तियों के बीच संशोधित किया गया था"। आप उस मामले की जांच करने के लिए अपना कोड निम्नलिखित में बदल सकते हैं। यह संग्रह के गणक पर MoveNext() पर कॉल करके ऐसा करता है, भले ही यह e.Current का उपयोग न करे, ताकि संग्रह में परिवर्तन होने पर विधि फेंक दे।

अनुमोदित, यह अनुकूलन का एक महत्वपूर्ण हिस्सा हटा देता है: कि गणनाकर्ता को आंशिक रूप से कदम उठाने और निपटान करने की आवश्यकता है, लेकिन यह अभी भी लाभ है कि आपको पहले count ऑब्जेक्ट्स के माध्यम से बिना किसी कदम के कदम उठाने की आवश्यकता नहीं है । और यह भ्रमित हो सकता है कि आपके पास e.Current है जो उपयोगी नहीं है, क्योंकि यह list[i] के बजाय list[i - count] पर इंगित करता है।

public static IEnumerable<T> Skip<T>(this IEnumerable<T> source, int count) 
{ 
    using (IEnumerator<T> e = source.GetEnumerator()) 
    { 
     if (source is IList<T>) 
     { 
      IList<T> list = (IList<T>)source; 
      for (int i = count; i < list.Count; i++) 
      { 
       e.MoveNext(); 
       yield return list[i]; 
      } 
     } 
     else if (source is IList) 
     { 
      IList list = (IList)source; 
      for (int i = count; i < list.Count; i++) 
      { 
       e.MoveNext(); 
       yield return (T)list[i]; 
      } 
     } 
     else 
     { 
      // .NET framework 
      while (count > 0 && e.MoveNext()) count--; 
      if (count <= 0) 
      { 
       while (e.MoveNext()) yield return e.Current; 
      } 
     } 
    } 
} 
संबंधित मुद्दे