2012-02-09 12 views
6

IEnumerable गारंटी नहीं देता है कि दो बार गणना करने से एक ही परिणाम मिलेगा। वास्तव में, यह एक उदाहरण बनाने के लिए काफी आसान है जहां myEnumerable.First() विभिन्न मूल्यों लौटाता है जब दो बार मार डाला:क्या "गतिशील" IENumerable "ठीक" करने का कोई कैननिक तरीका है?

class A { 
    public A(string value) { Value = value; } 
    public string Value {get; set; } 
} 

static IEnumerable<A> getFixedIEnumerable() { 
    return new A[] { new A("Hello"), new A("World") }; 
} 

static IEnumerable<A> getDynamicIEnumerable() { 
    yield return new A("Hello"); 
    yield return new A("World"); 
} 

static void Main(string[] args) 
{ 
    IEnumerable<A> fix = getFixedIEnumerable(); 
    IEnumerable<A> dyn = getDynamicIEnumerable(); 

    Console.WriteLine(fix.First() == fix.First()); // true 
    Console.WriteLine(dyn.First() == dyn.First()); // false 
} 

यह सिर्फ एक अकादमिक उदाहरण नहीं है: लोकप्रिय from ... in ... select new A(...) का उपयोग वास्तव में इस स्थिति पैदा करेगा। यह अप्रत्याशित व्यवहार करने के लिए नेतृत्व कर सकते हैं:

fix.First().Value = "NEW"; 
Console.WriteLine(fix.First().Value); // prints NEW 

dyn.First().Value = "NEW"; 
Console.WriteLine(dyn.First().Value); // prints Hello 

मैं समझता हूँ कि ऐसा क्यों होता। मुझे यह भी पता है कि यह ToList() निष्पादन योग्य या == को A के लिए ओवरराइड करके निष्पादित करके तय किया जा सकता है। यह मेरा सवाल नहीं है।

सवाल यह है कि: जब आप एक ऐसी विधि लिखते हैं जो मनमाने ढंग से IENumerable लेता है और आप उस संपत्ति को चाहते हैं जो अनुक्रम का मूल्यांकन केवल एक बार किया जाता है (और फिर संदर्भ "निश्चित" होते हैं), ऐसा करने का कैननिक तरीका क्या है? ToList() अधिकतर उपयोग किया जाता है, लेकिन यदि स्रोत पहले से तय किया गया है (उदाहरण के लिए, यदि स्रोत एक सरणी है), संदर्भों को एक सूची में कॉपी किया जाता है (अनावश्यक रूप से, क्योंकि मुझे केवल निश्चित संपत्ति की आवश्यकता है)। क्या इस मुद्दे के लिए कुछ और उपयुक्त है या ToList() "कैननिकल" समाधान है?

+1

परिभाषा के अनुसार एक आईनेमरेबल नहीं कहता है कि ऑर्डर की गारंटी नहीं है? यदि आपके कार्यान्वयन के लिए आदेश आवश्यक है तो आप गलत डेटाटाइप का उपयोग कर रहे हैं, नहीं? आप जो भी अपने अनुबंध में आईएन्यूमेरेबल के साथ पूछ रहे हैं वह यह है कि तत्वों को ट्रैव किया जा सकता है आईई: जैसा कि आप कहते हैं, एक सूची बेहतर अनुकूल है। –

उत्तर

3

ToList बहुत निश्चित रूप से जाने का तरीका है।

यह कुछ अनुकूलन है: अगर इनपुट गणन है एक ICollection (जैसे एक सरणी या सूची) यह वास्तव में गणना के बजाय ICollection.CopyTo फोन एक सरणी के लिए संग्रह कॉपी करने के लिए होगा - तो लागत जब तक संग्रह महत्वपूर्ण होने की संभावना नहीं है बहुत बड़ा है।

IMHO, सामान्य रूप में यह बेहतर है सबसे तरीकों वापस जाने के लिए के लिए ICollection<T> या IList<T> बजाय IEnumerable<T>, जब तक आप विधि है कि कार्यान्वयन आलसी मूल्यांकन (जैसे उपज) का उपयोग कर सकते के उपभोक्ताओं के लिए संकेत करना चाहते हैं।

ऐसे मामलों में जहां एक विधि को एक अपरिवर्तनीय सूची वापस करनी चाहिए, एक रीडोनली रैपर (ReadOnlyCollection<T>) उदा। ToList().AsReadOnly() पर कॉल करके, लेकिन अभी भी इंटरफ़ेस प्रकार IList<T> लौटाएं।

यदि आप इस दिशानिर्देश का पालन करते हैं, तो विधि के उपभोक्ताओं को कभी भी ToList पर अनावश्यक कॉल की आवश्यकता नहीं होगी।

2

आपका मतलब है कि यदि आपका फ़ंक्शन पैरामीटर लेता है और आप यह सुनिश्चित करना चाहते हैं कि पैरामीटर आलसी नहीं है, क्योंकि आपको विधि में एक से अधिक बार फिर से इसे फिर से चालू करने की आवश्यकता है? मैं आमतौर पर पैरामीटर को उस मामले में ICollection बना देता हूं, जो इसे आलसी होने पर गणना करने के लिए कॉलर की ज़िम्मेदारी बना देता है।

0

जिस तरह से आपने अपना प्रश्न समझाया है, ऐसा लगता है कि आप एक अनुक्रम को कई बार गिनना चाहते हैं और हर बार एक ही ऑर्डर प्राप्त करना चाहते हैं। इसका तात्पर्य है कि आप बाद में पुनरावृत्तियों को निष्पादित करने के अनुक्रम के संदर्भ में हैं, या आप एक ही विधि के भीतर कई बार तुलना कर रहे हैं।

आम तौर पर, IEnumerable<T> को स्वीकार करते समय, जिसका अर्थ है कि आप एक से अधिक बार संदर्भ या हेरफेर करना चाहते हैं, अनुक्रम को आंतरिक प्रतिनिधित्व में कॉपी करना सुरक्षित है। मेरी प्राथमिकता ToList() एक्सटेंशन का उपयोग करना है, क्योंकि यह अनुमानित क्रम के साथ बहुत अच्छी तरह से समझा जा सकता है और संभवतः सबसे सरल संग्रह है।

आंतरिक रूप से, जब भी "सामान्य" संग्रह की आवश्यकता होती है तो .NET Framework List<T> का उपयोग करता है।

1

जोएल स्पॉस्की ने Law of Leaky Abstractions में सबसे आम समस्या बताई है। आपकी विधि में IEnumerable पैरामीटर होने के बाद आप उस ऑब्जेक्ट की अपेक्षा करते हैं जिसे केवल समझा जा सकता है और कुछ भी नहीं। लेकिन फिर भी आप पारित संग्रह की परवाह करते हैं चाहे वह "निश्चित" या "गतिशील" हो। आप देख सकते हैं कि IEnumerable द्वारा बनाए गए अमूर्त लीक हो गए हैं। कोई समाधान नहीं है जो सभी मामलों के लिए उपयुक्त है। आपके मामले में आप IEnumerable के बजाय List<T> या T[] (या आपकी विधि में पैरामीटर के लिए वास्तविक प्रकार होने की अपेक्षा करते हैं) को पास कर सकते हैं। सबसे आम सलाह है कि आप अपने अमूर्तता को समझें और इसके संबंध में अपना कोड तैयार करें।

2

IEnumerable इंटरफ़ेस केवल तत्वों के माध्यम से पुनरावृत्ति करने के लिए "एक तरीका" प्रदान करता है (एक linq क्वेरी से IENumerable बनाना एक आदर्श उदाहरण है IEnumerable डेटाबेस के लिए SQL क्वेरी की तरह है)। यह तब तक गतिशील रहेगा जब तक आप परिणाम को ICollection (ToList, ToArray) में संग्रहीत न करें, इसलिए पुनरावृत्ति को अंत तक संसाधित किया जाता है और परिणाम "निश्चित" तरीके से संग्रहीत होता है।