कोविरियन और कंट्रोवर्सी मूल्य प्रकार का समर्थन क्यों नहीं करते हैं


149

IEnumerable<T>है सह संस्करण , लेकिन यह मान प्रकार, बस केवल संदर्भ के प्रकार का समर्थन नहीं करता। नीचे दिया गया सरल कोड सफलतापूर्वक संकलित किया गया है:

IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;

लेकिन से बदल रहा है stringकरने के लिए intसंकलित त्रुटि प्राप्त होगी:

IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList;

MSDN में इसका कारण बताया गया है :

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

मैंने खोज की है और पाया है कि कुछ प्रश्नों का उल्लेख कारण मूल्य प्रकार और संदर्भ प्रकार के बीच बॉक्सिंग है । लेकिन यह अभी भी मेरे दिमाग को साफ नहीं करता है कि मुक्केबाजी का कारण क्या है?

क्या कोई कृपया एक सरल और विस्तृत विवरण दे सकता है कि सहसंयोजक और विरोधाभासी मूल्य प्रकार का समर्थन क्यों नहीं करते हैं और मुक्केबाजी कैसे प्रभावित करती है?


3
मेरे इसी सवाल का एरिक का जवाब भी देखें: stackoverflow.com/questions/4096299/…
thorn12

जवाबों:


126

मूल रूप से, विचरण तब लागू होता है जब सीएलआर यह सुनिश्चित कर सकता है कि उसे मूल्यों के लिए कोई प्रतिनिधित्वात्मक परिवर्तन करने की आवश्यकता नहीं है । संदर्भ सभी समान दिखते हैं - इसलिए आप प्रतिनिधित्व में किसी भी बदलाव के बिना एक के IEnumerable<string>रूप में उपयोग कर सकते हैं IEnumerable<object>; मूल कोड को यह जानने की आवश्यकता नहीं है कि आप मूल्यों के साथ क्या कर रहे हैं, इसलिए जब तक बुनियादी ढांचे ने गारंटी दी है कि यह निश्चित रूप से मान्य होगा।

मान प्रकारों के लिए, जो काम नहीं करता है - ए के IEnumerable<int>रूप में इलाज करने के लिए IEnumerable<object>, अनुक्रम का उपयोग करने वाले कोड को यह जानना होगा कि बॉक्सिंग रूपांतरण करना है या नहीं।

आप सामान्य रूप से इस विषय पर अधिक के लिए प्रतिनिधित्व और पहचान पर एरिक लिपर्ट के ब्लॉग पोस्ट को पढ़ना चाह सकते हैं ।

EDIT: एरिक के ब्लॉग को स्वयं पोस्ट करने के बाद, यह कम से कम प्रतिनिधित्व के रूप में पहचान के बारे में है, हालांकि दोनों जुड़े हुए हैं। विशेष रूप से:

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


5
@CuongLe: वैसे यह कुछ इंद्रियों में एक कार्यान्वयन विवरण है, लेकिन यह प्रतिबंध का अंतर्निहित कारण है, मेरा मानना ​​है।
जॉन स्कीट

2
@ AndréCaron: एरिक का ब्लॉग पोस्ट यहां महत्वपूर्ण है - यह न केवल प्रतिनिधित्व है, बल्कि पहचान संरक्षण भी है। लेकिन प्रतिनिधित्व संरक्षण का अर्थ है कि उत्पन्न कोड को इस बारे में बिल्कुल भी ध्यान रखने की आवश्यकता नहीं है।
जॉन स्कीट

1
संक्षेप में, पहचान को संरक्षित नहीं किया जा सकता क्योंकि intइसका उपप्रकार नहीं है object। तथ्य यह है कि एक प्रतिनिधित्वत्मक परिवर्तन की आवश्यकता है, यह केवल इसका एक परिणाम है।
एंड्रे कारन

3
Int कैसे ऑब्जेक्ट का उपप्रकार नहीं है? Int32 को System.ValueType से विरासत में मिला है, जो System.Object से विरासत में मिला है।
डेविड क्लेम्फनर

1
@DavidKlempfner मुझे लगता है कि @ AndréCaron टिप्पणी खराब रूप से परिभाषित है। किसी भी मूल्य प्रकार जैसे कि Int32दो प्रतिनिधित्वात्मक रूप हैं, "बॉक्सिंग" और "अनबॉक्स"। संकलक को एक रूप से दूसरे में बदलने के लिए कोड सम्मिलित करना पड़ता है, भले ही यह आमतौर पर स्रोत कोड स्तर पर अदृश्य हो। वास्तव में, केवल "बॉक्सिंग" फॉर्म को अंतर्निहित सिस्टम द्वारा उप-प्रकार माना जाता है object, लेकिन कंपाइलर स्वचालित रूप से इससे निपटता है जब भी एक मूल्य प्रकार को एक संगत इंटरफ़ेस या किसी प्रकार का कुछ सौंपा जाता है object
स्टीव

10

यह समझना आसान है कि क्या आप अंतर्निहित प्रतिनिधित्व के बारे में सोचते हैं (भले ही यह वास्तव में एक कार्यान्वयन विवरण है)। यहाँ तार का एक संग्रह है:

IEnumerable<string> strings = new[] { "A", "B", "C" };

आप stringsनिम्न प्रतिनिधित्व के रूप में सोच सकते हैं :

[०]: स्ट्रिंग संदर्भ -> "ए"
[१]: स्ट्रिंग संदर्भ -> "बी"
[२]: स्ट्रिंग संदर्भ -> "सी"

यह तीन तत्वों का एक संग्रह है, प्रत्येक एक स्ट्रिंग का संदर्भ है। आप इसे वस्तुओं के संग्रह में डाल सकते हैं:

IEnumerable<object> objects = (IEnumerable<object>) strings;

मूल रूप से यह एक ही प्रतिनिधित्व है सिवाय अब संदर्भ वस्तु संदर्भ हैं:

[०]: वस्तु संदर्भ -> "ए"
[१]: वस्तु संदर्भ -> "बी"
[२]: वस्तु संदर्भ -> "सी"

प्रतिनिधित्व वही है। संदर्भों को बस अलग तरह से व्यवहार किया जाता है; अब string.Lengthआप संपत्ति तक नहीं पहुंच सकते हैं लेकिन आप अभी भी कॉल कर सकते हैं object.GetHashCode()। इसकी तुलना किलों के संग्रह से करें:

IEnumerable<int> ints = new[] { 1, 2, 3 };
[०]: int = १
[१]: इंट = २
[२]: इंट = ३

इसे IEnumerable<object>डेटा में परिवर्तित करने के लिए किलों को बॉक्सिंग में बदलना होगा :

[०]: वस्तु संदर्भ -> १
[१]: वस्तु संदर्भ -> २
[२]: वस्तु संदर्भ -> ३

इस रूपांतरण के लिए कलाकारों की तुलना में अधिक आवश्यकता होती है।


2
मुक्केबाजी केवल एक "कार्यान्वयन विवरण" नहीं है। बॉक्सेड वैल्यू टाइप को क्लास ऑब्जेक्ट्स की तरह ही स्टोर किया जाता है, और व्यवहार किया जाता है, जहां तक ​​बाहरी दुनिया बता सकती है, क्लास ऑब्जेक्ट्स की तरह। अंतर केवल इतना है कि एक बॉक्स्ड वैल्यू टाइप की परिभाषा के भीतर, thisएक ऐसी संरचना को संदर्भित करता है जिसके क्षेत्र उन ढेर ऑब्जेक्ट को ओवरले करते हैं जो इसे संग्रहीत करता है, बजाय उस ऑब्जेक्ट का संदर्भ देने के जो उन्हें धारण करता है। एक बॉक्सिंग मूल्य प्रकार उदाहरण के लिए कोई साफ रास्ता नहीं है जो एनक्लोजिंग हीप ऑब्जेक्ट का संदर्भ प्राप्त करे।
सुपरकैट

7

मुझे लगता है कि सब कुछ LSP(लिसकोव प्रतिस्थापन सिद्धांत) से शुरू होता है , जो क्लिम्स:

यदि q (x) टाइप T के ऑब्जेक्ट्स x के बारे में साबित होने वाली संपत्ति है, तो टाइप S के ऑब्जेक्ट्स के लिए q (y) सही होना चाहिए जहां S, T का उप-प्रकार है।

लेकिन मूल्य प्रकार, उदाहरण के लिए intकी विकल्प नहीं हो सकता है objectमें C#। सिद्ध बहुत सरल है:

int myInt = new int();
object obj1 = myInt ;
object obj2 = myInt ;
return ReferenceEquals(obj1, obj2);

falseयदि हम वस्तु को समान "संदर्भ" प्रदान करते हैं तो भी यह रिटर्न होता है ।


1
मुझे लगता है कि आप सही सिद्धांत का उपयोग कर रहे हैं, लेकिन बनाने के लिए कोई प्रमाण नहीं है: इसलिए यह intउपप्रकार नहीं है, objectइसलिए सिद्धांत लागू नहीं होता है। आपका "प्रमाण" एक मध्यवर्ती प्रतिनिधित्व पर निर्भर करता है Integer, जो इसका एक उपप्रकार है objectऔर जिसके लिए भाषा का एक अंतर्निहित रूपांतरण है ( object obj1=myInt;यह वास्तविक रूप से विस्तारित है object obj1=new Integer(myInt);)।
आंद्रे कारन

भाषा प्रकारों के बीच सही कास्टिंग का ख्याल रखती है, लेकिन ints व्यवहार उस चीज़ के अनुरूप नहीं है, जिसे हम ऑब्जेक्ट के उपप्रकार से उम्मीद करेंगे।
तिगरान

मेरा पूरा बिंदु ठीक यही intहै कि इसका उपप्रकार नहीं है object। इसके अलावा, एलएसपी लागू नहीं होता है myInt, obj1और obj2तीन अलग-अलग वस्तुओं को संदर्भित करता है: एक intऔर दो (छिपा हुआ) Integer
एंड्रे कारन

22
@ एंड्रे: सी # जावा नहीं है। C # का intकीवर्ड BCL के लिए एक अन्य नाम है System.Int32, जो वास्तव में object(एक उपनाम System.Object) का उपप्रकार है । वास्तव में, intआधार वर्ग वह है System.ValueTypeजो आधार वर्ग है System.Object। निम्नलिखित अभिव्यक्ति का मूल्यांकन करने का प्रयास करें और देखें typeof(int).BaseType.BaseType:। यहाँ कारण ReferenceEqualsगलत है कि intदो अलग बक्से में बॉक्सिंग की जाती है, और प्रत्येक बॉक्स की पहचान किसी अन्य बॉक्स के लिए अलग होती है। इस प्रकार दो बॉक्सिंग ऑपरेशन हमेशा दो वस्तुओं का उत्पादन करते हैं जो कभी भी समान नहीं होते हैं, चाहे बॉक्सिंग मूल्य हो।
एलोन गुरिलनेक

@AllonGuralnek: प्रत्येक मान प्रकार (जैसे System.Int32या List<String>.Enumerator) वास्तव में दो प्रकार की चीजों का प्रतिनिधित्व करता है: एक भंडारण-स्थान प्रकार, और एक ढेर-वस्तु प्रकार (कभी-कभी "बॉक्सिंग मूल्य प्रकार" कहा जाता है)। भंडारण स्थान जिनके प्रकार System.ValueTypeपूर्व से प्राप्त होते हैं ; ढेर वस्तुओं जिनके प्रकार इसी तरह बाद में पकड़ लेंगे। अधिकांश भाषाओं में, एक व्यापक कास्ट पूर्व से मौजूद है, और बाद से पूर्व तक एक संकुचित कास्ट है। ध्यान दें कि बॉक्सिंग वैल्यू टाइप्स में वैल्यू-टाइप स्टोरेज लोकेशन की तरह ही डिस्क्रिप्टर होता है, ...
सुपरकैट

3

यह एक कार्यान्वयन विवरण के लिए नीचे आता है: मान प्रकार को संदर्भ प्रकारों के लिए अलग तरीके से लागू किया जाता है।

यदि आप मूल्य प्रकारों को संदर्भ प्रकारों के रूप में मानने के लिए बाध्य करते हैं (जैसे कि उन्हें बॉक्स करें, उदाहरण के लिए इंटरफ़ेस के माध्यम से उन्हें संदर्भित करके) तो आप विचरण प्राप्त कर सकते हैं।

अंतर को देखने का सबसे आसान तरीका केवल इस पर विचार करना है Array: स्मृति प्रकार की एक सरणी को समसामयिक (सीधे) मेमोरी में एक साथ रखा जाता है, जहां संदर्भ प्रकारों की एक सरणी में केवल संदर्भ (एक सूचक) संदर्भ में स्मृति में होता है; इंगित की जा रही वस्तुओं को अलग से आवंटित किया जाता है।

अन्य (संबंधित) मुद्दा (*) यह है कि (लगभग) सभी संदर्भ प्रकारों में भिन्नता प्रयोजनों के लिए समान प्रतिनिधित्व है और बहुत कोड को प्रकारों के बीच के अंतर को जानने की आवश्यकता नहीं है, इसलिए सह और गर्भ-विचरण संभव है (और आसानी से) कार्यान्वित - अक्सर अतिरिक्त प्रकार की जाँच के चूक से)।

(*) इसे एक ही मुद्दा देखा जा सकता है ...

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.