2009-07-08 11 views
137

मुझे पता है कि सी # में "स्ट्रिंग" एक संदर्भ प्रकार है। यह एमएसडीएन पर है। हालांकि, इस कोड के रूप में काम नहीं करता है यह तो होना चाहिए:सी # स्ट्रिंग संदर्भ प्रकार?

class Test 
{ 
    public static void Main() 
    { 
     string test = "before passing"; 
     Console.WriteLine(test); 
     TestI(test); 
     Console.WriteLine(test); 
    } 

    public static void TestI(string test) 
    { 
     test = "after passing"; 
    } 
} 

उत्पादन "पार करने से पहले" "होना चाहिए" पास करने के बाद से मैं एक पैरामीटर के रूप स्ट्रिंग गुजर रहा हूँ और यह एक संदर्भ प्रकार किया जा रहा है, दूसरे आउटपुट स्टेटमेंट को यह समझना चाहिए कि पाठ टेस्टआई विधि में बदल गया है। हालांकि, मुझे लगता है कि "गुजरने से पहले" "गुजरने से पहले" मिलता है, ऐसा लगता है कि यह मान द्वारा पारित किया जाता है न कि रेफरी द्वारा। मैं समझता हूं कि तार अपरिवर्तनीय हैं, लेकिन मुझे नहीं पता कि यह कैसे समझाएगा कि यह क्या हो रहा है। मैं क्या खो रहा हूँ? धन्यवाद।

+0

नीचे दिए गए लेख द्वारा दिए गए लेख को देखें। आपके द्वारा उल्लिखित व्यवहार को सी ++ पॉइंटर्स द्वारा भी पुन: उत्पन्न किया जा सकता है। – Sesh

+0

[एमएसडीएन] में बहुत अच्छी व्याख्या (http://msdn.microsoft.com/en-us/library/s6938f28.aspx) भी। –

+0

संभावित सी डुप्लिकेट [सी # में, स्ट्रिंग एक संदर्भ प्रकार क्यों है जो मान प्रकार की तरह व्यवहार करता है?] (Http://stackoverflow.com/questions/636932/in-c-why-is-string-a-reference-type -that-behaves-like-a-value-type) – Liam

उत्तर

185

स्ट्रिंग का संदर्भ मान द्वारा पारित किया जाता है। संदर्भ द्वारा किसी संदर्भ को पारित करने और संदर्भ द्वारा ऑब्जेक्ट पास करने के बीच एक बड़ा अंतर है। यह दुर्भाग्यपूर्ण है कि दोनों संदर्भों में "संदर्भ" शब्द का उपयोग किया जाता है।

यदि आप संदर्भ द्वारा स्ट्रिंग संदर्भ पारित करते हैं, यह आपकी अपेक्षा के अनुसार काम करेगा:

using System; 

class Test 
{ 
    public static void Main() 
    { 
     string test = "before passing"; 
     Console.WriteLine(test); 
     TestI(ref test); 
     Console.WriteLine(test); 
    } 

    public static void TestI(ref string test) 
    { 
     test = "after passing"; 
    } 
} 

अब आप वस्तु जो एक संदर्भ को संदर्भित करता है में परिवर्तन करने के बीच अंतर करने की जरूरत है, और किसी भिन्न ऑब्जेक्ट को संदर्भित करने के लिए एक चर (जैसे पैरामीटर) में परिवर्तन करना। हम क्योंकि तार अपरिवर्तनीय हैं एक स्ट्रिंग में परिवर्तन कर सकते नहीं है, लेकिन हम एक StringBuilder बजाय साथ यह प्रदर्शित कर सकते हैं:

using System; 
using System.Text; 

class Test 
{ 
    public static void Main() 
    { 
     StringBuilder test = new StringBuilder(); 
     Console.WriteLine(test); 
     TestI(test); 
     Console.WriteLine(test); 
    } 

    public static void TestI(StringBuilder test) 
    { 
     // Note that we're not changing the value 
     // of the "test" parameter - we're changing 
     // the data in the object it's referring to 
     test.Append("changing"); 
    } 
} 

अधिक जानकारी के लिए my article on parameter passing देखें।

+2

सहमत हैं, बस यह स्पष्ट करना चाहते हैं कि रेफ संशोधक का उपयोग गैर-संदर्भ प्रकारों के लिए भी काम करता है यानी दोनों अलग-अलग अवधारणाएं हैं। – eglasius

+2

@ जोन स्कीट आपके लेख में sidenote प्यार करता था। आपको 'संदर्भित' होना चाहिए था कि आपका उत्तर –

0

प्रयास करें:


public static void TestI(ref string test) 
    { 
     test = "after passing"; 
    } 
+1

आपके उत्तर में केवल कोड से अधिक होना चाहिए। इसमें एक स्पष्टीकरण भी होना चाहिए कि यह क्यों काम करता है। –

9

वास्तव में यह उस बात अर्थात एक संदर्भ प्रकार किया जा रहा है और संदर्भ द्वारा पारित करने के लिए किसी भी वस्तु के लिए एक ही हो गया होता ग # में 2 अलग बातें हैं।

यह काम करेगा, लेकिन उस प्रकार की परवाह किए बिना लागू होता है:

public static void TestI(ref string test) 

इसके अलावा के बारे में स्ट्रिंग एक संदर्भ प्रकार, इसकी भी एक विशेष से एक होने के। यह अपरिवर्तनीय होने के लिए डिज़ाइन किया गया है, इसलिए इसके सभी तरीके उदाहरण को संशोधित नहीं करेंगे (वे एक नया लौटाते हैं)। इसमें प्रदर्शन के लिए इसमें कुछ अतिरिक्त चीजें भी हैं।

25

अगर हमें प्रश्न का उत्तर देना है: स्ट्रिंग एक संदर्भ प्रकार है और यह संदर्भ के रूप में व्यवहार करता है। हम एक पैरामीटर पास करते हैं जिसमें संदर्भ होता है, वास्तविक स्ट्रिंग नहीं। समस्या समारोह में है:

public static void TestI(string test) 
{ 
    test = "after passing"; 
}

पैरामीटर test स्ट्रिंग के लिए एक संदर्भ रखती है, लेकिन यह एक प्रति है। हमारे पास स्ट्रिंग को इंगित करने वाले दो चर हैं। और क्योंकि स्ट्रिंग्स के साथ कोई भी ऑपरेशन वास्तव में एक नई वस्तु बनाता है, इसलिए हम अपनी स्थानीय प्रतिलिपि को नई स्ट्रिंग पर इंगित करने के लिए बनाते हैं। लेकिन मूल test चर बदल नहीं है।

फ़ंक्शन घोषणा में और आमंत्रण कार्य में ref डालने के लिए सुझाए गए समाधान क्योंकि हम test चर के मान को पास नहीं करेंगे लेकिन इसके संदर्भ में केवल एक संदर्भ पारित करेंगे। इस प्रकार फ़ंक्शन के अंदर कोई भी परिवर्तन मूल चर को प्रतिबिंबित करेगा।

मैं अंत में दोहराना चाहते हैं: स्ट्रिंग एक संदर्भ प्रकार है, लेकिन इसके अपरिवर्तनीय लाइन test = "after passing"; के बाद से वास्तव में एक नई वस्तु और चर test से बाहर कॉपी बन जाती नया स्ट्रिंग को इंगित करने के लिए बदल गया है।

0

मेरा मानना ​​है कि अपने कोड निम्नलिखित के अनुरूप है, और आप मूल्य की उम्मीद नहीं करनी चाहिए इसी कारण से बदल दिया है इसे यहाँ नहीं होगा:

public static void Main() 
{ 
    StringWrapper testVariable = new StringWrapper("before passing"); 
    Console.WriteLine(testVariable); 
    TestI(testVariable); 
    Console.WriteLine(testVariable); 
} 

public static void TestI(StringWrapper testParameter) 
{ 
    testParameter = new StringWrapper("after passing"); 

    // this will change the object that testParameter is pointing/referring 
    // to but it doesn't change testVariable unless you use a reference 
    // parameter as indicated in other answers 
} 
6

यहाँ के बारे में सोचना एक अच्छा तरीका है मूल्य-प्रकार, गुजरने वाले मूल्य, संदर्भ-प्रकार, और पास-बाय-रेफरेंस के बीच अंतर:

एक चर एक कंटेनर है।

एक मूल्य-प्रकार चर में एक उदाहरण होता है। एक संदर्भ-प्रकार चर में कहीं और संग्रहीत एक उदाहरण के लिए एक सूचक शामिल है।

एक मान-प्रकार चर परिवर्तक को संशोधित करने वाले उदाहरण को बदल देता है। संदर्भ-प्रकार चर को संशोधित करने से उस उदाहरण को बदल दिया जाता है जो यह इंगित करता है।

पृथक संदर्भ-प्रकार चर एक ही उदाहरण को इंगित कर सकते हैं। इसलिए, वही उदाहरण किसी भी चर के माध्यम से उत्परिवर्तित किया जा सकता है जो इसे इंगित करता है।

एक पास-दर-मूल्य तर्क सामग्री की एक नई प्रति के साथ एक नया कंटेनर है। एक पारित संदर्भ तर्क मूल सामग्री के साथ मूल कंटेनर है।

जब कोई मान-प्रकार तर्क पास-दर-मान होता है: तर्क की सामग्री को पुन: असाइन करने से दायरे के बाहर कोई प्रभाव नहीं पड़ता है, क्योंकि कंटेनर अद्वितीय है। तर्क को संशोधित करने से दायरे के बाहर कोई प्रभाव नहीं पड़ता है, क्योंकि उदाहरण एक स्वतंत्र प्रति है।

जब कोई संदर्भ-प्रकार तर्क पास-दर-मान होता है: तर्क की सामग्री को पुन: असाइन करने से दायरे के बाहर कोई प्रभाव नहीं पड़ता है, क्योंकि कंटेनर अद्वितीय है। तर्क की सामग्री को संशोधित करना बाहरी दायरे को प्रभावित करता है, क्योंकि प्रतिलिपि सूचक एक साझा उदाहरण को इंगित करता है।

जब कोई तर्क पास-दर-संदर्भ है: तर्क की सामग्री को पुन: असाइन करना बाहरी दायरे को प्रभावित करता है, क्योंकि कंटेनर साझा किया जाता है। तर्क की सामग्री को संशोधित करना बाहरी दायरे को प्रभावित करता है, क्योंकि सामग्री साझा की जाती है।

अंत में:

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

18

जैसा कि अन्य ने कहा है, String .NET में टाइप अपरिवर्तनीय है और इसका संदर्भ मूल्य से गुजरता है।

मूल कोड में, जैसे ही इस लाइन को निष्पादित करता है के रूप में:

test = "after passing"; 

तो test नहीं रह गया है मूल वस्तु को जिक्र है। हमने नयाString ऑब्जेक्ट बनाया है और प्रबंधित ढेर पर उस ऑब्जेक्ट को संदर्भित करने के लिए test असाइन किया है।

मुझे लगता है कि बहुत से लोग यहां पहुंचे हैं क्योंकि उन्हें याद दिलाने के लिए कोई औपचारिक कन्स्ट्रक्टर नहीं है। इस मामले में, यह दृश्यों के पीछे हो रहा है क्योंकि String प्रकार के निर्माण के तरीके में भाषा समर्थन है।

इसलिए, test में परिवर्तन TestI(string) विधि के दायरे से बाहर दिखाई नहीं दे रहा है - हमने मूल्य से संदर्भ पारित कर दिया है और अब वह मान बदल गया है! लेकिन अगर String संदर्भ संदर्भ द्वारा पारित किया गया था, तो जब संदर्भ बदल गया तो हम इसे TestI(string) विधि के दायरे से बाहर देखेंगे।

या तो रेफरी या बाहर कीवर्ड इस मामले में की जरूरत है। मुझे लगता है कि out कीवर्ड इस विशेष स्थिति के लिए थोड़ा बेहतर अनुकूल हो सकता है।

class Program 
{ 
    static void Main(string[] args) 
    { 
     string test = "before passing"; 
     Console.WriteLine(test); 
     TestI(out test); 
     Console.WriteLine(test); 
     Console.ReadLine(); 
    } 

    public static void TestI(out string test) 
    { 
     test = "after passing"; 
    } 
} 
2

से ऊपर जवाब सहायक होते हैं, मैं तो बस एक उदाहरण मुझे लगता है कि प्रदर्शन कर रहा है स्पष्ट रूप से क्या होता है जब हम रेफरी कीवर्ड बिना पैरामीटर गुजरते हैं जो पैरामीटर एक संदर्भ प्रकार है तब भी जब जोड़ना चाहते हैं:

MyClass c = new MyClass(); c.MyProperty = "foo"; 

CNull(c); // only a copy of the reference is sent 
Console.WriteLine(c.MyProperty); // still foo, we only made the copy null 
CPropertyChange(c); 
Console.WriteLine(c.MyProperty); // bar 


private void CNull(MyClass c2) 
     {   
      c2 = null; 
     } 
private void CPropertyChange(MyClass c2) 
     { 
      c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well. 
     }