मुझे पता है कि .NET में संरचनाएं वंशानुक्रम का समर्थन नहीं करती हैं, लेकिन यह बिल्कुल स्पष्ट नहीं है कि वे इस तरह से सीमित क्यों हैं।
क्या तकनीकी कारण संरचना को अन्य संरचनाओं से विरासत में मिलने से रोकता है?
मुझे पता है कि .NET में संरचनाएं वंशानुक्रम का समर्थन नहीं करती हैं, लेकिन यह बिल्कुल स्पष्ट नहीं है कि वे इस तरह से सीमित क्यों हैं।
क्या तकनीकी कारण संरचना को अन्य संरचनाओं से विरासत में मिलने से रोकता है?
जवाबों:
कारण मान प्रकार इनहेरिटेंस का समर्थन नहीं कर सकते क्योंकि एरे के कारण है।
समस्या यह है कि, प्रदर्शन और जीसी कारणों के लिए, मूल्य प्रकार के सरणियों को "इनलाइन" संग्रहीत किया जाता है। उदाहरण के लिए, दिया गया new FooType[10] {...}
, यदि FooType
कोई संदर्भ प्रकार है, तो प्रबंधित हीप पर 11 ऑब्जेक्ट बनाए जाएंगे (सरणी के लिए एक, और प्रत्येक प्रकार के उदाहरण के लिए 10)। यदि FooType
इसके बजाय एक मूल्य प्रकार है, तो प्रबंधित हीप पर केवल एक उदाहरण बनाया जाएगा - सरणी के लिए (जैसा कि प्रत्येक सरणी मान को सरणी के साथ "इनलाइन" संग्रहीत किया जाएगा)।
अब, मान लीजिए कि हमारे पास मूल्य प्रकारों के साथ विरासत थी। जब सरणियों के उपरोक्त "इनलाइन स्टोरेज" व्यवहार के साथ संयुक्त, खराब चीजें होती हैं, जैसा कि सी ++ में देखा जा सकता है ।
इस छद्म सी # कोड पर विचार करें:
struct Base
{
public int A;
}
struct Derived : Base
{
public int B;
}
void Square(Base[] values)
{
for (int i = 0; i < values.Length; ++i)
values [i].A *= 2;
}
Derived[] v = new Derived[2];
Square (v);
सामान्य रूपांतरण नियमों के अनुसार, एक (बेहतर या बदतर के लिए) के लिए Derived[]
परिवर्तनीय है Base[]
, इसलिए यदि आप उपरोक्त उदाहरण के लिए / संरचना / वर्ग / जी हैं, तो यह बिना किसी समस्या के, उम्मीद के अनुसार संकलन और चलाएगा। लेकिन अगर Base
और Derived
मूल्य प्रकार हैं, और सरणियों में स्टोर इनलाइन हैं, तो हमारे पास एक समस्या है।
हमारे पास एक समस्या है क्योंकि Square()
इसके बारे में कुछ भी नहीं पता है Derived
, यह सरणी के प्रत्येक तत्व तक पहुंचने के लिए केवल सूचक अंकगणितीय का उपयोग करेगा, एक स्थिर राशि ( sizeof(A)
) द्वारा बढ़ाना । विधानसभा अस्पष्ट होगी:
for (int i = 0; i < values.Length; ++i)
{
A* value = (A*) (((char*) values) + i * sizeof(A));
value->A *= 2;
}
(हां, यह घिनौना असेंबली है, लेकिन मुद्दा यह है कि हम ज्ञात संकलन-समय स्थिरांक पर सरणी के माध्यम से वेतन वृद्धि करेंगे, बिना किसी ज्ञान के कि एक व्युत्पन्न प्रकार का उपयोग किया जा रहा है।)
इसलिए, यदि वास्तव में ऐसा हुआ है, तो हमारे पास भ्रष्टाचार के मुद्दों की स्मृति होगी। विशेष रूप से, के भीतर Square()
, values[1].A*=2
होगा वास्तव में बदलाव हो values[0].B
!
डिबग करने की कोशिश करो कि !
कल्पना कीजिए कि आधारभूत संरचना विरासत में मिली है। फिर घोषणा:
BaseStruct a;
InheritedStruct b; //inherits from BaseStruct, added fields, etc.
a = b; //?? expand size during assignment?
इसका मतलब यह होगा कि संरचित चर का निश्चित आकार नहीं होता है, और इसीलिए हमारे पास संदर्भ प्रकार होते हैं।
इससे भी बेहतर, इस पर विचार करें:
BaseStruct[] baseArray = new BaseStruct[1000];
baseArray[500] = new InheritedStruct(); //?? morph/resize the array?
Foo
इनहेरिट करने के लिए Bar
a Foo
को असाइन करने की अनुमति नहीं दी जानी चाहिए Bar
, लेकिन एक ऐसे तरीके की घोषणा करने से कुछ उपयोगी प्रभाव पड़ सकते हैं: (1) Bar
पहले आइटम के रूप में विशेष रूप से नामित सदस्य बनाएं Foo
और इसमें Foo
शामिल करें सदस्य के नाम जो उन सदस्यों के लिए अन्य नाम हैं Bar
, जो कोड का उपयोग Bar
करने के लिए अनुकूलित किया गया था Foo
, बिना किसी संदर्भ के thing.BarMember
साथ सभी संदर्भों को बदलने के लिए thing.theBar.BarMember
, और Bar
एक समूह के रूप में सभी क्षेत्रों को पढ़ने और लिखने की क्षमता बनाए रखने के लिए ; ...
संरचनाएं संदर्भों का उपयोग नहीं करती हैं (जब तक कि वे बॉक्सिंग नहीं हैं, लेकिन आपको इससे बचने की कोशिश करनी चाहिए) इस प्रकार बहुरूपता सार्थक नहीं है क्योंकि संदर्भ सूचक के माध्यम से कोई अप्रत्यक्ष नहीं है। आमतौर पर वस्तुएं ढेर पर रहती हैं और संदर्भ बिंदुओं के माध्यम से संदर्भित की जाती हैं, लेकिन स्टैक पर संरचनाएं आवंटित की जाती हैं (जब तक कि वे बॉक्सिंग नहीं होती हैं) या ढेर पर एक संदर्भ प्रकार द्वारा कब्जा की गई मेमोरी के अंदर "आवंटित" किया जाता है।
Foo
, जिसमें संरचना का एक क्षेत्र होता है, जो सदस्यों के स्वयं के रूप में Bar
संबंध रखने में सक्षम होता है Bar
, ताकि एक Point3d
वर्ग उदा सकता है, Point2d xy
लेकिन या X
तो उस क्षेत्र को संदर्भित कर सकता है । xy.X
X
यहाँ डॉक्स का कहना है:
संरचनाएं विशेष रूप से छोटे डेटा संरचनाओं के लिए उपयोगी होती हैं जिनके मूल्य शब्दार्थ हैं। जटिल संख्या, एक समन्वित प्रणाली में बिंदु, या एक शब्दकोश में कुंजी-मूल्य जोड़े संरचना के सभी अच्छे उदाहरण हैं। इन डेटा संरचनाओं की कुंजी यह है कि उनके पास कुछ डेटा सदस्य हैं, कि उन्हें वंशानुक्रम या संदर्भात्मक पहचान के उपयोग की आवश्यकता नहीं है, और यह कि उन्हें आसानी से मूल्य शब्दार्थ का उपयोग करके लागू किया जा सकता है जहां असाइनमेंट संदर्भ के बजाय मान की प्रतिलिपि बनाता है।
मूल रूप से, वे सरल डेटा रखने वाले हैं और इसलिए इनहेरिटेंस जैसी "अतिरिक्त सुविधाएँ" नहीं हैं। यह संभव है कि तकनीकी रूप से उनके लिए कुछ सीमित प्रकार की विरासतों का समर्थन करना संभव हो (न कि बहुरूपता, उनके कारण स्टैक पर होने के कारण), लेकिन मेरा मानना है कि यह एक डिजाइन विकल्प भी है कि विरासत का समर्थन न करें (.NET में कई अन्य चीजों की तरह)। भाषाएँ हैं।)
दूसरी ओर, मैं विरासत के लाभों से सहमत हूं, और मुझे लगता है कि हम सभी उस बिंदु पर पहुंच गए हैं, जहां हम चाहते हैं कि हम struct
दूसरे से विरासत में लें, और महसूस करें कि यह संभव नहीं है। लेकिन उस बिंदु पर, डेटा संरचना शायद इतनी उन्नत है कि इसे वैसे भी एक वर्ग होना चाहिए।
Point3D
से निर्माण करने के लिए जोड़कर Point2D
; आप सक्षम नहीं होंगे) के Point3D
बजाय का उपयोग करने के लिए Point2D
, लेकिन आप Point3D
खरोंच से पूरी तरह से फिर से लागू करने के लिए नहीं होगा ।) कि कैसे मैं इसे वैसे भी व्याख्या की है ...
class
पर किसी व्यक्ति को चुनने में मदद करने के लिए यह एक डिज़ाइन विकल्प है struct
।
वंशानुक्रम जैसा वर्ग संभव नहीं है, क्योंकि एक संरचना सीधे ढेर पर रखी जाती है। एक अंतर्निहित संरचना तब बड़ी होगी, लेकिन यह माता-पिता की है, लेकिन जेआईटी को पता नहीं है, और बहुत कम जगह पर बहुत अधिक डालने की कोशिश करता है। थोड़ा अस्पष्ट लगता है, चलो एक उदाहरण लिखते हैं:
struct A {
int property;
} // sizeof A == sizeof int
struct B : A {
int childproperty;
} // sizeof B == sizeof int * 2
यदि यह संभव होगा, तो यह निम्नलिखित स्निपेट पर क्रैश होगा:
void DoSomething(A arg){};
...
B b;
DoSomething(b);
अंतरिक्ष को आकार ए के लिए आवंटित किया जाता है, आकार बी के लिए नहीं।
एक बिंदु है जिसे मैं सही करना चाहूंगा। भले ही कारण संरचनाएं विरासत में नहीं मिल सकती हैं, क्योंकि वे स्टैक पर रहते हैं सही एक है, यह एक ही आधा सही स्पष्टीकरण पर है। संरचनाएं, किसी भी अन्य प्रकार की तरह, स्टैक में रह सकती हैं। क्योंकि यह इस पर निर्भर करेगा जहां चर घोषित किया जाता है कि वे या तो जीना होगा ढेर या में ढेर । यह तब होगा जब वे क्रमशः स्थानीय चर या उदाहरण क्षेत्र होंगे।
यह कहते हुए कि, सेसिल हैज़ नेम ने इसे सही ढंग से दर्ज किया है।
मैं इस पर जोर देना चाहूंगा, मूल्य प्रकार स्टैक पर रह सकते हैं। इसका मतलब यह नहीं है कि वे हमेशा ऐसा करते हैं। स्थानीय चर, विधि मापदंडों सहित, होगा। अन्य सभी नहीं करेंगे। फिर भी, यह अभी भी कारण है कि उन्हें विरासत में नहीं मिला है। :-)
स्टैक पर संरचनाएं आवंटित की जाती हैं। इसका मतलब यह है कि मूल्य शब्दार्थ बहुत मुक्त हैं, और संरचना सदस्यों तक पहुंचना बहुत सस्ता है। यह बहुरूपता को नहीं रोकता है।
आपके पास प्रत्येक संरचनात्मक शुरुआत सूचक के साथ इसकी आभासी फ़ंक्शन तालिका हो सकती है। यह एक प्रदर्शन मुद्दा होगा (प्रत्येक संरचना कम से कम एक पॉइंटर के आकार की होगी), लेकिन यह उल्लेखनीय है। यह आभासी कार्यों की अनुमति देगा।
खेतों को जोड़ने के बारे में क्या?
ठीक है, जब आप स्टैक पर एक संरचना आवंटित करते हैं, तो आप एक निश्चित मात्रा में स्थान आवंटित करते हैं। आवश्यक स्थान को संकलन समय (चाहे समय से पहले या जब JITting) में निर्धारित किया जाता है। यदि आप फ़ील्ड जोड़ते हैं और फिर एक आधार प्रकार को असाइन करते हैं:
struct A
{
public int Integer1;
}
struct B : A
{
public int Integer2;
}
A a = new B();
यह स्टैक के कुछ अज्ञात भाग को अधिलेखित कर देगा।
विकल्प रनटाइम के लिए होता है ताकि किसी भी ए वेरिएबल के लिए केवल आकार (ए) बाइट्स लिखकर इसे रोका जा सके।
यदि B A में एक विधि को ओवरराइड करता है और उसके Integer2 क्षेत्र को संदर्भित करता है तो क्या होगा? या तो रनटाइम एक मेसअसेक्स्टैप्शन फेंकता है, या इसके बजाय विधि स्टैक पर कुछ यादृच्छिक डेटा तक पहुंचती है। इनमें से कोई भी अनुमन्य नहीं है।
स्ट्रक्चर इनहेरिटेंस होना पूरी तरह से सुरक्षित है, इसलिए जब तक आप पॉलीमॉर्फिक रूप से स्ट्रक्चर्स का उपयोग नहीं करते हैं, या जब तक आप इनहेरिट करते समय फ़ील्ड नहीं जोड़ते हैं। लेकिन ये बहुत उपयोगी नहीं हैं।
यह एक बहुत लगातार सवाल की तरह लगता है। मुझे ऐसा लगता है कि मान जोड़ने के लिए "जहाँ आप वैरिएबल घोषित करते हैं वहाँ" टाइप किए जाते हैं; कार्यान्वयन विवरणों के अलावा, इसका मतलब है कि कोई ऑब्जेक्ट हेडर नहीं है जो ऑब्जेक्ट के बारे में कुछ कहता है, केवल चर जानता है कि किस तरह का डेटा वहां रहता है।
संरचनाएं समर्थन इंटरफेस करती हैं, इसलिए आप इस तरह से कुछ बहुरूपी चीजें कर सकते हैं।
IL एक स्टैक-आधारित भाषा है, इसलिए किसी तर्क के साथ एक विधि को कॉल करना कुछ इस तरह से होता है:
जब विधि चलती है, तो वह अपने तर्क को प्राप्त करने के लिए स्टैक से कुछ बाइट्स निकालती है। यह बिल्कुल पता है कितने बाइट को पॉप करने के लिए क्योंकि तर्क या तो एक संदर्भ प्रकार सूचक है (32-बिट पर हमेशा 4 बाइट्स) या यह एक मूल्य प्रकार है जिसके लिए आकार हमेशा सटीक रूप से जाना जाता है।
यदि यह एक रेफरेंस टाइप पॉइंटर है, तो विधि हीप में ऑब्जेक्ट को देखती है और उसका टाइप हैंडल प्राप्त होता है, जो एक मेथड टेबल की ओर इशारा करता है, जो उस सटीक टाइप के लिए उस विशेष विधि को हैंडल करता है। यदि यह एक मान प्रकार है, तो विधि तालिका के लिए कोई भी खोज आवश्यक नहीं है क्योंकि मूल्य प्रकार वंशानुक्रम का समर्थन नहीं करते हैं, इसलिए केवल एक ही संभव तरीका / प्रकार संयोजन है।
यदि मान प्रकार समर्थित वंशानुक्रम में है, तो इसमें अतिरिक्त ओवरहेड होगा, विशेष प्रकार की संरचना को स्टैक पर रखा जाएगा और साथ ही इसके मूल्य, जिसका अर्थ होगा कि विशेष प्रकार के ठोस उदाहरण के लिए विधि तालिका खोज। इससे मूल्य प्रकारों की गति और दक्षता लाभ समाप्त हो जाएंगे।