2010-07-02 5 views
43

हम सभी को सामान्य रूप से mutable structs are evil पता है। मुझे यह भी यकीन है कि IEnumerable<T>.GetEnumerator() रिटर्न IEnumerator<T> टाइप करता है, तो structs को तत्काल संदर्भ प्रकार में बॉक्स किया जाता है, यदि वे बस संदर्भ प्रकारों के साथ शुरू होते हैं।बीसीएल संग्रह क्यों संरचना गणनाकर्ताओं का उपयोग करते हैं, वर्ग नहीं?

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

भले ही मैं एक डिजाइन निर्णय से सहमत न हो, मैं इसके पीछे तर्क को समझने में सक्षम होना चाहता हूं।

+0

संबंधित पृष्ठों: http://stackoverflow.com/questions/384511/enumerator-implementation-use-struct-or-class http://www.eggheadcafe.com/software/ एस्पनेट/31702392/सी-कंपाइलर-चुनौती - s.aspx –

उत्तर

62

दरअसल, यह प्रदर्शन कारणों से है। बीसीएल टीम ने लॉट पर इस बिंदु पर शोध के पहले निर्णय लेने से पहले कि आप सही तरीके से एक संदिग्ध और खतरनाक अभ्यास के रूप में कॉल करने का निर्णय लेते हैं: एक परिवर्तनीय मूल्य प्रकार का उपयोग किया।

आप पूछते हैं कि यह मुक्केबाजी का कारण क्यों नहीं है। ऐसा इसलिए है क्योंकि सी # कंपाइलर फोरैच लूप में आईन्यूमेरेबल या आईन्यूमेरेटर को बॉक्स स्टफ पर कोड जेनरेट नहीं करता है अगर यह इससे बच सकता है!

जब हम

foreach(X x in c) 

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

इसमें दो वांछनीय प्रभाव हैं।

सबसे पहले, अगर संग्रह, स्याही का संग्रह कहता है, लेकिन जेनेरिक प्रकारों का आविष्कार करने से पहले लिखा गया था, तो यह मुक्केबाजी के मुक्केबाजी दंड को ऑब्जेक्ट करने के लिए वर्तमान के मूल्य को नहीं लेता है और फिर इसे int में अनबॉक्स कर रहा है। यदि वर्तमान एक ऐसी संपत्ति है जो एक int लौटाती है, तो हम इसका इस्तेमाल करते हैं।

दूसरा, यदि गणनाकर्ता एक मान प्रकार है तो यह एननेमेरेटर के लिए गणनाकर्ता को बॉक्स नहीं करता है।

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

उदाहरण के लिए, इस पर विचार करें:

struct MyHandle : IDisposable { ... } 
... 
using (MyHandle h = whatever) 
{ 
    h = somethingElse; 
} 

आप काफी ठीक ही विफल ज उत्परिवर्तित करने का प्रयास उम्मीद होती है, और वास्तव में यह होता है। कंपाइलर का पता लगाता है कि आप किसी ऐसे लंबित निपटारे के मूल्य को बदलने की कोशिश कर रहे हैं, जिसमें ऐसा लंबित निपटान है, और ऐसा करने से ऑब्जेक्ट का निपटारा करने की आवश्यकता हो सकती है जिसे वास्तव में निपटान नहीं किया जा सकता है।

अब मान लीजिए कि आप था:

struct MyHandle : IDisposable { ... } 
... 
using (MyHandle h = whatever) 
{ 
    h.Mutate(); 
} 

यहाँ क्या होता है? आप उचित रूप से उम्मीद कर सकते हैं कि संकलक ऐसा करेगा यदि एच एक पाठक क्षेत्र था: make a copy, and mutate the copy यह सुनिश्चित करने के लिए कि विधि उस मूल्य में सामान को फेंक न दे जिस पर निपटान की आवश्यकता है। यह है चाहे

using (Enumerator enumtor = whatever) 
{ 
    ... 
    enumtor.MoveNext(); 
    ... 
} 

हमें उम्मीद है कि एक का उपयोग कर ब्लॉक अंदर एक MoveNext कर अगले एक करने के लिए प्रगणक पर आ जाएगा:

हालांकि, इस बारे में हमारे अंतर्ज्ञान के साथ संघर्ष क्या यहाँ होने के लिए चाहिए एक संरचना या एक रेफ प्रकार।

दुर्भाग्यवश, सी # कंपाइलर में आज एक बग है। यदि आप इस स्थिति में हैं तो हम चुनते हैं कि कौन सी रणनीति असंगत रूप से पालन करें। व्यवहार आज:

  • यदि मान टाइप चर जा रहा है एक विधि के माध्यम से उत्परिवर्तित एक सामान्य स्थानीय तो यह सामान्य रूप से उत्परिवर्तित

  • है, लेकिन अगर यह एक फहराया स्थानीय है (यह एक closed- है, क्योंकि एक अज्ञात फ़ंक्शन या इटेटरेटर ब्लॉक में परिवर्तनीय) तो स्थानीय वास्तव में केवल पढ़ने के लिए फ़ील्ड के रूप में उत्पन्न होता है, और गियर जो सुनिश्चित करता है कि एक प्रतिलिपि पर उत्परिवर्तन होता है।

दुर्भाग्यवश इस मामले पर spec थोड़ा मार्गदर्शन प्रदान करता है। स्पष्ट रूप से कुछ तोड़ दिया गया है क्योंकि हम इसे असंगत रूप से कर रहे हैं, लेकिन सही क्या करना है बिल्कुल स्पष्ट नहीं है। इस पर दौड़ते हुए दूसरों के लिए

+1

+1 इसका मतलब यह है कि मूल जेनेरिक संग्रह के विपरीत 'nnumerable ' के आस-पास गुजरने की न्यूनतम (न्यूनतम) प्रदर्शन दंड - एक त्वरित रिलीज-मोड परीक्षण में 'दस0 प्रविष्टियों में से एक सूची ''पर उल्लिखित है और जब 'IENumerable ' पर डाला जाता है, तो मुझे 2: 1 का एक सतत समय अंतर दिखाई देता है (इस मामले में, ~ 100ms बनाम ~ 50ms)। –

+0

अच्छा जवाब, मुझे उस अनुकूलन के बारे में पता नहीं था - लेकिन यह सही समझ में आता है। मुझे यह कुछ हद तक विडंबना मिलती है कि मैंने आपके ब्लॉग को मेरे बयान का बैकअप लेने के लिए जोड़ा है कि उत्परिवर्तनीय structs बुरा हैं - और आप मेरे प्रश्न का उत्तर देते हैं :) – Eloff

+0

बीटीडब्ल्यू, यह एक बदसूरत कोने का मामला है, फिर भी परिवर्तनीय structs के साथ एक और समस्या है। – Eloff

5

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

+0

लेकिन ये आंतरिक structs हैं, इसलिए प्रकार संकलन समय पर कभी ज्ञात नहीं है; और सभी एंड-यूज़र कोड इंटरफेस के माध्यम से उन्हें एक्सेस करते हैं। –

+1

@Stephen: 'सूची । एन्यूमेरेटर' को एमएसडीएन में सार्वजनिक होने के रूप में दस्तावेज किया गया है ... –

+0

यदि आप उदाहरण के लिए देखें .GetEnunmerator विधि (http://msdn.microsoft.com/en-us/library/b0yss765 .aspx) आप इसे देख सकते हैं सूची :: गणनाकर्ता संरचना। सी # में फ़ोरैच लूप सीधे आईनेमेरेबल इंटरफ़ेस का उपयोग नहीं करता है, अगर कक्षा में GetEnumerator विधि है तो यह इसके लिए तैयार है। इसलिए गणना का समय संकलन समय पर जाना जाता है। – STO

संबंधित मुद्दे