.NET में स्ट्रिंग्स को कैसे पास किया जाता है?


121

जब मैं stringकिसी फ़ंक्शन को पास करता हूं , तो स्ट्रिंग की सामग्री के लिए एक संकेतक पास किया जाता है, या स्टैक पर फ़ंक्शन के लिए पूरे स्ट्रिंग को पारित किया जाता है जैसे structहोगा?

जवाबों:


278

एक संदर्भ पारित किया गया है; हालाँकि, यह तकनीकी रूप से संदर्भ से पारित नहीं है यह एक सूक्ष्म, लेकिन बहुत महत्वपूर्ण अंतर है। निम्नलिखित कोड पर विचार करें:

void DoSomething(string strLocal)
{
    strLocal = "local";
}
void Main()
{
    string strMain = "main";
    DoSomething(strMain);
    Console.WriteLine(strMain); // What gets printed?
}

यहां क्या होता है, इसे समझने के लिए आपको तीन बातें जानने की जरूरत है:

  1. स्ट्रिंग्स C # में संदर्भ प्रकार हैं।
  2. वे अपरिवर्तनीय भी हैं, इसलिए किसी भी समय आप ऐसा कुछ करते हैं जो ऐसा लगता है जैसे आप स्ट्रिंग को बदल रहे हैं, आप नहीं हैं। एक पूरी तरह से नया स्ट्रिंग बनाया जाता है, इस पर संदर्भ को इंगित किया जाता है, और पुराने को फेंक दिया जाता है।
  3. भले ही तार संदर्भ प्रकार हैं, strMainसंदर्भ द्वारा पारित नहीं किया गया है। यह एक संदर्भ प्रकार है, लेकिन संदर्भ स्वयं मूल्य द्वारा पारित किया जाता है । जब भी आप refकीवर्ड के बिना एक पैरामीटर पास करते हैं ( outमापदंडों की गिनती नहीं करते हैं ), तो आपने मूल्य से कुछ पारित किया है।

तो इसका मतलब यह होना चाहिए कि आप ... एक संदर्भ मूल्य से गुजर रहे हैं। चूंकि यह एक संदर्भ प्रकार है, केवल संदर्भ को स्टैक पर कॉपी किया गया था। लेकिन इसका क्या मतलब है?

मान द्वारा संदर्भ प्रकार पास करना: आप पहले से ही ऐसा कर रहे हैं

C # चर या तो संदर्भ प्रकार या मान प्रकार हैं । सी # मानकों या तो कर रहे हैं संदर्भ द्वारा पारित या मूल्य द्वारा पारित । शब्दावली यहां एक समस्या है; एक ही चीज़ की तरह ये ध्वनि, लेकिन वे नहीं हैं।

यदि आप किसी भी प्रकार का पैरामीटर पास करते हैं, और आप refकीवर्ड का उपयोग नहीं करते हैं , तो आप इसे मूल्य से पास कर चुके हैं। यदि आपने इसे मूल्य से पास कर दिया है, तो आप वास्तव में जो पास किया है वह एक कॉपी था। लेकिन अगर पैरामीटर एक संदर्भ प्रकार था, तो आपके द्वारा कॉपी की गई चीज संदर्भ थी, न कि वह जो इंगित कर रही थी।

यहाँ Mainविधि की पहली पंक्ति है :

string strMain = "main";

हमने इस पंक्ति में दो चीजें बनाई हैं: एक स्ट्रिंग जिसमें mainमेमोरी में कहीं स्टोर की गई है, और एक संदर्भ चर जिसे strMainइंगित किया जाता है।

DoSomething(strMain);

अब हम उस संदर्भ को पास करते हैं DoSomething। हमने इसे मान से पास कर दिया है, इसलिए इसका मतलब है कि हमने एक प्रति बना ली है। यह एक संदर्भ प्रकार है, इसलिए इसका अर्थ है कि हमने संदर्भ की प्रतिलिपि बनाई है, न कि स्ट्रिंग की। अब हमारे पास दो संदर्भ हैं जो प्रत्येक मेमोरी में एक ही मान के लिए इंगित करते हैं।

कैली के अंदर

यहाँ DoSomethingविधि का शीर्ष है :

void DoSomething(string strLocal)

कोई refकीवर्ड नहीं , इसलिए strLocalऔर strMainदो अलग-अलग संदर्भ एक ही मूल्य पर इंगित कर रहे हैं। अगर हम फिर से भरोसा strLocal...

strLocal = "local";   

... हमने संग्रहीत मूल्य नहीं बदला है; हमने बुलाया संदर्भ लिया strLocalऔर इसे एक नए स्ट्रिंग में लक्षित किया। strMainजब हम ऐसा करते हैं तो क्या होता है? कुछ भी तो नहीं। यह अभी भी पुराने तार की ओर इशारा कर रहा है।

string strMain = "main";    // Store a string, create a reference to it
DoSomething(strMain);       // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main" 

अचल स्थिति

चलो एक दूसरे के लिए परिदृश्य बदलते हैं। कल्पना करें कि हम स्ट्रिंग्स के साथ काम नहीं कर रहे हैं, लेकिन कुछ परस्पर संदर्भ प्रकार, जैसे आपके द्वारा बनाई गई कक्षा।

class MutableThing
{
    public int ChangeMe { get; set; }
}

यदि आप उस ऑब्जेक्ट के संदर्भ का अनुसरण करते हैं objLocal, जो आपको इंगित करता है, तो आप इसके गुणों को बदल सकते हैं:

void DoSomething(MutableThing objLocal)
{
     objLocal.ChangeMe = 0;
} 

MutableThingस्मृति में अभी भी केवल एक ही है , और प्रतिलिपि किए गए संदर्भ और मूल संदर्भ दोनों अभी भी इसे इंगित करते हैं। खुद के गुण MutableThingबदल गए हैं :

void Main()
{
    var objMain = new MutableThing();
    objMain.ChangeMe = 5; 
    Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain

    DoSomething(objMain);                // now it's 0 on objLocal
    Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain   
}

आह, लेकिन तार अपरिवर्तनीय हैं! ChangeMeसेट करने के लिए कोई संपत्ति नहीं है । आप strLocal[3] = 'H'सी # में नहीं कर सकते हैं जैसे आप सी-स्टाइल charसरणी के साथ कर सकते हैं ; आपको इसके बजाय एक पूरी नई स्ट्रिंग बनानी होगी। बदलने strLocalका एकमात्र तरीका संदर्भ को दूसरे स्ट्रिंग पर इंगित करना है, और इसका मतलब है कि आप जो कुछ भी कर strLocalसकते हैं वह प्रभावित नहीं कर सकता है strMain। मान अपरिवर्तनीय है, और संदर्भ एक प्रति है।

संदर्भ से एक संदर्भ गुजर रहा है

यह साबित करने के लिए कि अंतर क्या है, जब आप संदर्भ द्वारा एक संदर्भ देते हैं , तो यहां क्या होता है :

void DoSomethingByReference(ref string strLocal)
{
    strLocal = "local";
}
void Main()
{
    string strMain = "main";
    DoSomethingByReference(ref strMain);
    Console.WriteLine(strMain);          // Prints "local"
}

इस बार, स्ट्रिंग Mainवास्तव में बदल जाती है क्योंकि आपने इसे स्टैक पर कॉपी किए बिना संदर्भ पारित किया है।

इसलिए, भले ही तार संदर्भ प्रकार हों, लेकिन उन्हें मान से गुजरने का मतलब है कि कॉलली में जो कुछ भी होता है वह कॉलर में स्ट्रिंग को प्रभावित नहीं करेगा। लेकिन जब से वे कर रहे हैं संदर्भ प्रकार, आप जब आप इसे चारों ओर पास करना चाहते हैं स्मृति में पूरी स्ट्रिंग कॉपी करने के लिए नहीं है।

आगे के संसाधन:


3
@ TheLight - क्षमा करें, लेकिन जब आप कहते हैं कि आप यहां गलत हैं: "एक संदर्भ प्रकार डिफ़ॉल्ट रूप से संदर्भ द्वारा पारित किया जाता है।" डिफ़ॉल्ट रूप से, सभी पैरामीटर मान द्वारा पारित किए जाते हैं, लेकिन संदर्भ प्रकारों के साथ, इसका मतलब है कि संदर्भ मूल्य द्वारा पारित किया गया है। आप संदर्भ मापदंडों के साथ संदर्भ प्रकारों को भ्रमित कर रहे हैं, जो समझ में आता है क्योंकि यह बहुत ही भ्रमित करने वाला अंतर है। यहां मान अनुभाग द्वारा पासिंग संदर्भ प्रकार देखें आपका लिंक किया गया लेख काफी सही है, लेकिन यह वास्तव में मेरी बात का समर्थन करता है।
जस्टिन मॉर्गन

1
@JustinMorgan एक मृत टिप्पणी धागा लाने के लिए नहीं है, लेकिन मुझे लगता है कि TheLight की टिप्पणी से समझ में आता है यदि आप सी। सी में सोचते हैं, तो डेटा सिर्फ मेमोरी का एक ब्लॉक है। एक संदर्भ मेमोरी के उस ब्लॉक का एक संकेतक है। यदि आप किसी फ़ंक्शन में मेमोरी का पूरा ब्लॉक पास करते हैं, तो उसे "वैल्यू बाइ पासिंग" कहा जाता है। यदि आप पॉइंटर पास करते हैं, तो इसे "संदर्भ से गुजरना" कहा जाता है। C # में, मेमोरी के पूरे ब्लॉक में पास होने की कोई धारणा नहीं है, इसलिए उन्होंने पॉइंटर को पास करने के लिए "पासिंग वैल्यू" को फिर से परिभाषित किया। इसका मतलब गलत है, लेकिन एक पॉइंटर सिर्फ मेमोरी का एक ब्लॉक भी है! मेरे लिए, शब्दावली बहुत मनमानी है
rliu

@roliu - समस्या यह है कि हम C में काम नहीं कर रहे हैं, और C # अपने समान नाम और वाक्य रचना के बावजूद बेहद अलग है। एक बात के लिए, संदर्भ बिंदु के समान नहीं हैं , और उनके बारे में सोचने से इस तरह के नुकसान हो सकते हैं। हालाँकि, सबसे बड़ी समस्या यह है कि "पासिंग रेफरेंस" का C # में बहुत ही विशिष्ट अर्थ है, जिसे refकीवर्ड की आवश्यकता है । यह साबित करने के लिए कि संदर्भ से गुजरने से फर्क पड़ता है, इस डेमो को देखें: rextester.com/WKBG5978
जस्टिन मॉर्गन

1
@JustinMorgan मैं सहमत हूं कि C और C # शब्दावली को मिलाना बुरा है, लेकिन, जब मैंने लिपर्ट के पद का आनंद लिया, तो मैं इस बात से सहमत नहीं हूं कि संदर्भों के बारे में सोच विशेष रूप से यहां कुछ भी धूमिल करती है। ब्लॉग पोस्ट बताती है कि एक पॉइंटर के रूप में एक संदर्भ के बारे में सोचना कितना अधिक शक्ति देता है। मुझे पता है कि refकीवर्ड की उपयोगिता है, मैं बस यह समझाने की कोशिश कर रहा था कि कोई व्यक्ति C # में मान द्वारा संदर्भ प्रकार पास करने के बारे में क्यों सोच सकता है, ऐसा लगता है जैसे संदर्भ से गुजरने की "पारंपरिक" (यानी C) धारणा (और संदर्भ प्रकार पास करना) C के संदर्भ में # ऐसा लगता है जैसे मान द्वारा किसी संदर्भ को भेजना अधिक पसंद है)।
रियालू

2
आप सही हैं, लेकिन मुझे लगता है @roliu संदर्भित किया गया था कि कैसे एक समारोह के रूप में Foo(string bar)के रूप में सोचा जा सकता है Foo(char* bar), जबकि Foo(ref string bar)होगा Foo(char** bar)(या Foo(char*& bar)या Foo(string& bar)C ++)। ज़रूर, यह नहीं है कि आपको इसे हर रोज कैसे सोचना चाहिए, लेकिन इससे वास्तव में मुझे यह समझने में मदद मिली कि हुड के नीचे क्या हो रहा है।
कोल जॉनसन

23

C # में स्ट्रिंग्स अपरिवर्तनीय संदर्भ ऑब्जेक्ट हैं। इसका मतलब है कि उनके संदर्भ (मूल्य के अनुसार) पास हो गए हैं, और एक बार स्ट्रिंग बनाने के बाद, आप इसे संशोधित नहीं कर सकते। स्ट्रिंग (संशोधित, छंटनी किए गए संस्करण, आदि) के संशोधित संस्करण उत्पन्न करने वाले तरीके मूल स्ट्रिंग की संशोधित प्रतियां बनाते हैं ।


10

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

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


4
इस पहलू में स्ट्रिंग्स कोई विशेष मामला नहीं है। अपरिवर्तनीय वस्तुओं को बनाना बहुत आसान है जो एक ही शब्दार्थ हो सकता है। (अर्थात, एक प्रकार का उदाहरण जो इसे उत्परिवर्तित करने के लिए एक विधि को उजागर नहीं करता है ...)

स्ट्रिंग्स विशेष मामले हैं - वे प्रभावी रूप से अपरिवर्तनीय संदर्भ प्रकार हैं जो कि इस तरह के परस्पर व्यवहार करते हैं कि वे मूल्य प्रकारों की तरह व्यवहार करते हैं।
एनग्मेटीविटी

1
@Enigmativity उस तर्क से तब Uri(वर्ग) और Guid(संरचना) भी विशेष मामले हैं। मैं यह नहीं देखता कि System.Stringअन्य अपरिवर्तनीय प्रकारों की तुलना में "वैल्यू टाइप" की तरह काम कैसे किया जाता है ... या तो क्लास या स्ट्रक्चर ओरिजिन।

3
@pst - स्ट्रिंग्स का विशेष सृजन शब्दार्थ है - इसके विपरीत - Uriऔर Guidआप स्ट्रिंग स्ट्रिंग के लिए केवल एक स्ट्रिंग-शाब्दिक मान असाइन कर सकते हैं। स्ट्रिंग intपुन: असाइन किया जा रहा है , जैसा प्रतीत होता है , लेकिन यह एक वस्तु का निर्माण कर रहा है - कोई newकीवर्ड नहीं ।
एनगैमिटिविटी

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