Java / C # RAII क्यों लागू नहीं कर सकता है?


29

प्रश्न: Java / C # RAII क्यों लागू नहीं कर सकता है?

स्पष्टीकरण: मुझे पता है कि कचरा संग्राहक निर्धारक नहीं है। तो वर्तमान भाषा सुविधाओं के साथ यह किसी वस्तु के निपटान () विधि के लिए गुंजाइश से बाहर निकलने पर स्वचालित रूप से बुलाया जाना संभव नहीं है। लेकिन क्या ऐसी नियतवादी सुविधा जोड़ी जा सकती है?

मेरी समझ:

मुझे लगता है कि RAII का कार्यान्वयन दो आवश्यकताओं को पूरा करना चाहिए:
1. एक संसाधन का जीवनकाल एक दायरे के लिए बाध्य होना चाहिए।
2. लागू। संसाधन का मुक्तकरण प्रोग्रामर द्वारा स्पष्ट विवरण के बिना होना चाहिए। एक स्पष्ट कथन के बिना एक कचरा कलेक्टर मुक्त मेमोरी के अनुरूप। "निहितार्थ" को केवल कक्षा के उपयोग के बिंदु पर होना चाहिए। कक्षा पुस्तकालय निर्माता को निश्चित रूप से विध्वंसक या निपटान () विधि को स्पष्ट रूप से लागू करना चाहिए।

जावा / सी # संतुष्ट बिंदु 1. सी # में आईडीआईसोपेरी को लागू करने वाला एक संसाधन "गुंजाइश" का उपयोग करने के लिए बाध्य हो सकता है:

void test()
{
    using(Resource r = new Resource())
    {
        r.foo();
    }//resource released on scope exit
}

यह बिंदु 2 को संतुष्ट नहीं करता है। प्रोग्रामर को स्पष्ट रूप से ऑब्जेक्ट को एक विशेष "स्कोप" का उपयोग करना चाहिए। प्रोग्रामर एक रिसाव पैदा कर, संसाधन को एक दायरे में स्पष्ट रूप से बाँधना भूल सकते हैं।

वास्तव में संकलक द्वारा "उपयोग" ब्लॉक को अंततः-कोशिश-निपटान () कोड में बदल दिया जाता है। यह कोशिश-अंत-निपटान () पैटर्न का एक ही स्पष्ट प्रकृति है। एक अंतर्निहित रिलीज के बिना, एक स्कोप का हुक सिंटैक्टिक चीनी है।

void test()
{
    //Programmer forgot (or was not aware of the need) to explicitly
    //bind Resource to a scope.
    Resource r = new Resource(); 
    r.foo();
}//resource leaked!!!

मुझे लगता है कि यह जावा / सी # में एक भाषा सुविधा बनाने के लायक है, जो विशेष वस्तुओं को अनुमति देता है जो स्मार्ट-पॉइंटर के माध्यम से स्टैक को झुकाते हैं। सुविधा आपको एक वर्ग को स्कोप-बाउंड के रूप में फ़्लैग करने की अनुमति देगी, ताकि यह हमेशा हुक से स्टैक के साथ बनाया जाए। विभिन्न प्रकार के स्मार्ट पॉइंटर्स के लिए विकल्प हो सकते हैं।

class Resource - ScopeBound
{
    /* class details */

    void Dispose()
    {
        //free resource
    }
}

void test()
{
    //class Resource was flagged as ScopeBound so the tie to the stack is implicit.
    Resource r = new Resource(); //r is a smart-pointer
    r.foo();
}//resource released on scope exit.

मुझे लगता है कि निहितार्थ "इसके लायक" है। जिस तरह कचरा संग्रह का निहितार्थ "इसके लायक" है। स्पष्ट उपयोग करने वाले ब्लॉक आंखों पर ताज़ा होते हैं, लेकिन कोशिश-अंत-निपटान () पर कोई अर्थ लाभ नहीं मिलता है।

क्या जावा / सी # भाषाओं में ऐसी सुविधा को लागू करना अव्यावहारिक है? क्या पुराने कोड को तोड़े बिना इसे पेश किया जा सकता है?


3
यह अव्यावहारिक नहीं है, यह असंभव है । सी # मानक विनाशकर्ता की गारंटी नहीं देता / Disposeएस कर रहे हैं कभी चलाने के लिए, कि वे किस तरह शुरू हो रहे हैं की परवाह किए बिना। दायरे के अंत में निहित विनाश को जोड़ने से मदद नहीं मिलेगी।
तेलस्टिन

20
@ टेलस्टाइन हुह? C # मानक का कहना है कि अब कोई प्रासंगिकता नहीं है, क्योंकि हम उस बहुत ही दस्तावेज़ को बदलने पर चर्चा कर रहे हैं। केवल मुद्दा है कि क्या यह करने के लिए व्यावहारिक है, और के लिए एक गारंटी की मौजूदा कमी के बारे में केवल दिलचस्प बिट है कि कारणों इस कमी के- गारंटी के लिए। ध्यान दें कि usingके निष्पादन के Dispose लिए गारंटी दी जाती है (ठीक है, बिना किसी अपवाद के अचानक मरने वाली प्रक्रिया को छूट देना, जिस बिंदु पर सभी सफाई संभवतः मूक हो जाती है)।

4
की नकल जावा के डेवलपर्स बूझकर आरए II का परित्याग किया था? , हालांकि स्वीकृत उत्तर पूरी तरह से गलत है। संक्षिप्त उत्तर यह है कि जावा मान (स्टैक) शब्दार्थ के बजाय संदर्भ (हीप) शब्दार्थ का उपयोग करता है , इसलिए निर्धारक अंतिमकरण बहुत उपयोगी / संभव नहीं है। सी # करता मूल्य अर्थ विज्ञान (है ), लेकिन वे आम तौर पर बहुत ही खास मामलों को छोड़कर परहेज कर रहे हैं। यह भी देखेंstruct
ब्लूराजा -

2
यह समान है, सटीक डुप्लिकेट नहीं है।
मनिएरो

3
blogs.msdn.com/b/oldnewthing/archive/2010/08/10/10048150.aspx इस प्रश्न का एक प्रासंगिक पृष्ठ है।
मणिरो

जवाबों:


17

ऐसा भाषा विस्तार जितना आप सोचते हैं, उससे कहीं अधिक जटिल और आक्रामक होगा। आप सिर्फ जोड़ नहीं सकते

यदि स्टैक-बाउंड प्रकार के एक चर का जीवन-समय समाप्त होता है, तो Disposeउस ऑब्जेक्ट पर कॉल करें जो इसे संदर्भित करता है

भाषा कल्पना के संबंधित अनुभाग और किया जा सकता है। मैं अस्थायी मूल्यों ( new Resource().doSomething()) की समस्या को नजरअंदाज कर दूंगा जिसे थोड़ा और सामान्य शब्दों द्वारा हल किया जा सकता है, यह सबसे गंभीर मुद्दा नहीं है। उदाहरण के लिए, इस कोड को तोड़ दिया जाएगा (और इस तरह की बात शायद सामान्य रूप से असंभव हो जाती है):

File openSavegame(string id) {
    string path = ... id ...;
    File f = new File(path);
    // do something, perhaps logging
    return f;
} // f goes out of scope, caller receives a closed file

अब आपको उपयोगकर्ता द्वारा परिभाषित कॉपी कंस्ट्रक्टर्स (या कंस्ट्रक्टर्स को स्थानांतरित करना) की आवश्यकता है और उन्हें हर जगह लागू करना शुरू कर दें। न केवल यह प्रदर्शन के निहितार्थों को ले जाता है, यह इन चीजों को प्रभावी ढंग से मूल्य प्रकार भी बनाता है, जबकि लगभग सभी अन्य वस्तुएं संदर्भ प्रकार हैं। जावा के मामले में, यह एक कट्टरपंथी विचलन है कि ऑब्जेक्ट कैसे काम करते हैं। C # कम में तो (पहले से ही structs है, लेकिन उनके लिए कोई उपयोगकर्ता-परिभाषित प्रतिलिपि निर्माता AFAIK नहीं है), लेकिन यह अभी भी इन RAII वस्तुओं को और अधिक विशेष बनाता है। वैकल्पिक रूप से, रैखिक प्रकारों का एक सीमित संस्करण (cf. रस्ट) भी पैरामीटर को पारित करने सहित उर्फिंग को प्रतिबंधित करने की लागत पर समस्या को हल कर सकता है (जब तक कि आप रुस्ट-जैसे उधार संदर्भों और एक उधार चेकर को अपनाकर और भी अधिक जटिलता का परिचय नहीं देना चाहते)।

यह तकनीकी रूप से किया जा सकता है, लेकिन आप उन चीजों की एक श्रेणी के साथ समाप्त होते हैं जो भाषा में बाकी सब चीजों से बहुत अलग हैं। यह लगभग हमेशा एक बुरा विचार है, कार्यान्वयनकर्ताओं के लिए परिणाम (अधिक धार के मामले, प्रत्येक विभाग में अधिक समय / लागत) और उपयोगकर्ताओं (सीखने के लिए अधिक अवधारणाएं, बग की अधिक संभावना)। यह अतिरिक्त सुविधा के लायक नहीं है।


आपको कॉपी / मूव कंस्ट्रक्टर की आवश्यकता क्यों है? फाइल रेफरेंस टाइप है। उस स्थिति में f जो कि एक पॉइंटर है जिसे कॉल करने वाले को कॉपी किया जाता है और यह संसाधन को निपटाने के लिए जिम्मेदार है (संकलक इसके बजाय कॉलर में एक अंत में-डिस्पोजल पैटर्न डाल देगा)
Maniero

1
@bigown यदि आप Fileइस तरह से हर संदर्भ का इलाज करते हैं , तो कुछ भी नहीं बदलता है और Disposeइसे कभी नहीं बुलाया जाता है। यदि आप हमेशा फोन करते हैं Dispose, तो आप डिस्पोजेबल वस्तुओं के साथ कुछ नहीं कर सकते। या आप कभी-कभी डिस्पोज करने के लिए कुछ स्कीम का प्रस्ताव कर रहे हैं और कभी-कभी नहीं? यदि हां, तो कृपया इसका विस्तार से वर्णन करें और मैं आपको उन स्थितियों को बताऊंगा जिनमें यह विफल रहता है।

मैं यह देखने में विफल हूं कि आपने अब क्या कहा (मैं नहीं कह रहा कि आप गलत हैं)। ऑब्जेक्ट में एक संसाधन है, न कि संदर्भ।
मनिएरो

मेरी समझ, आप उदाहरण को सिर्फ एक रिटर्न में बदल रहे हैं, यह है कि संकलक संसाधन अधिग्रहण (आपके उदाहरण पर लाइन 3) और अंत के अंत से पहले ब्लॉक-लाइन (पंक्ति 6) से पहले एक कोशिश सम्मिलित करेगा। यहाँ कोई समस्या नहीं है, सहमत हैं? वापस अपने उदाहरण के लिए। कंपाइलर को एक ट्रांसफर दिखाई देता है, यह कोशिश-अंत में यहाँ सम्मिलित नहीं कर सकता है, लेकिन कॉल करने वाले को एक (पॉइंटर से) फ़ाइल ऑब्जेक्ट प्राप्त होगा और यह मानते हुए कि कॉलर इस ऑब्जेक्ट को फिर से स्थानांतरित नहीं कर रहा है, कंपाइलर कोशिश-अंत में वहाँ पैटर्न डालेगा। दूसरे शब्दों में कोशिश-अंत पैटर्न को लागू करने के लिए हर आईडीसॉरेबल ऑब्जेक्ट को हस्तांतरित करने की आवश्यकता नहीं है।
मनिएरो

1
@bigown दूसरे शब्दों में, Disposeअगर कोई संदर्भ बचता है तो कॉल न करें ? पलायन विश्लेषण एक पुरानी और कठिन समस्या है, यह हमेशा भाषा में अधिक बदलाव के बिना काम नहीं करेगा। जब एक संदर्भ को दूसरी (आभासी) विधि से पारित किया जाता है, तो गुंजाइश के अंत में कहा something.EatFile(f);जाना f.Disposeचाहिए? यदि हाँ, तो आप कॉलर्स को तोड़ते हैं जो fबाद में उपयोग के लिए स्टोर करते हैं। यदि नहीं, तो आप रिसोर्स को लीक करते हैं यदि कॉलर स्टोर नहीं करता है f। इसे हटाने का एकमात्र सरल तरीका एक रैखिक प्रकार की प्रणाली है, जो (जैसा कि मैंने पहले ही अपने उत्तर में बाद में चर्चा की थी) कई अन्य जटिलताओं का परिचय देता है।

26

जावा या सी # के लिए कुछ इस तरह से लागू करने में सबसे बड़ी कठिनाई यह होगी कि संसाधन हस्तांतरण कैसे काम करता है। आपको कार्यक्षेत्र से परे संसाधन के जीवन का विस्तार करने के लिए किसी तरह की आवश्यकता होगी। विचार करें:

class IWrapAResource
{
    private readonly Resource resource;
    public IWrapAResource()
    {
        // Where Resource is scope bound
        Resource builder = new Resource(args, args, args);

        this.resource = builder;
    } // Uh oh, resource is destroyed
} // Crap, there's no scope for IWrapAResource we can bind to!

इससे भी बुरी बात यह है कि इसे लागू करने वाले के लिए स्पष्ट नहीं हो सकता है IWrapAResource:

class IWrapSomething<T>
{
    private readonly T resource; // What happens if T is Resource?
    public IWrapSomething(T input)
    {
        this.resource = input;
    }
}

C # usingस्टेटमेंट जैसा कुछ संभवत: उतना ही करीब है जितना कि आप CII या C ++ जैसे हर जगह मूल्य गिनती के संसाधनों का उपयोग करने के लिए या मूल्य अर्थ विज्ञान को मजबूर करने के बिना RAII शब्दार्थ के लिए आने वाले हैं। क्योंकि जावा और C # में कूड़े के संग्रहकर्ता द्वारा प्रबंधित संसाधनों का निहित बंटवारा है, न्यूनतम प्रोग्रामर को ऐसा करने में सक्षम होने की आवश्यकता होती है, जिसके लिए एक संसाधन बाध्य होता है, जो वास्तव में usingपहले से ही करता है।


यह मानकर कि आपको दायरे से बाहर जाने के बाद एक चर का उल्लेख करने की आवश्यकता नहीं है (और वास्तव में ऐसी कोई आवश्यकता नहीं होनी चाहिए), मेरा दावा है कि आप अभी भी इसके लिए एक अंतिम रूप लिखकर किसी वस्तु को स्वयं बना सकते हैं । ऑब्जेक्ट को कचरा एकत्र करने से ठीक पहले अंतिम रूप दिया जाता है। देखें msdn.microsoft.com/en-us/library/0s71x931.aspx
रॉबर्ट हार्वे

8
@Robert: एक सही ढंग से लिखा गया प्रोग्राम कभी भी अंतिम रूप नहीं दे सकता। blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx
बिली

1
हम्म। खैर, शायद इसीलिए वे usingबयान के साथ आए ।
रॉबर्ट हार्वे

2
ठीक ठीक। यह C ++ में नौसिखिया बग का एक बड़ा स्रोत है, और जावा / सी # में भी होगा। जावा / सी # एक संसाधन के संदर्भ को नष्ट करने की क्षमता को खत्म नहीं करता है जो नष्ट होने वाला है, लेकिन इसे स्पष्ट और वैकल्पिक दोनों बनाकर वे प्रोग्रामर को याद दिलाते हैं और उसे सचेत करते हैं कि उसे क्या करना है।
१३:३४ पर अलेक्सांद्र डबिन्सकी

1
@svick यह IWrapSomethingनिपटाने के लिए नहीं है T। जिसने भी Tइस बारे में चिंता करने की आवश्यकता है, चाहे usingवह IDisposableस्वयं का हो, या कुछ तदर्थ संसाधन जीवनचक्र योजना का उपयोग कर रहा हो।
हांग्जो डबिन्सकी

13

कारण है कि RAII C # जैसी भाषा में काम नहीं कर सकता है, लेकिन यह C ++ में काम करता है, ऐसा इसलिए है क्योंकि C ++ में आप तय कर सकते हैं कि क्या कोई वस्तु वास्तव में अस्थायी है (स्टैक पर इसे आवंटित करके) या क्या यह लंबे समय से है (द्वारा बिंदुओं का उपयोग newऔर उपयोग करते हुए इसे ढेर पर आवंटित करना )।

तो, C ++ में, आप ऐसा कुछ कर सकते हैं:

void f()
{
    Foo f1;
    Foo* f2 = new Foo();
    Foo::someStaticField = f2;

    // f1 is destroyed here, the object pointed to by f2 isn't
}

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

आप जो कुछ कर सकते हैं, वह कुछ विशेष स्थानीय चर प्रकार का परिचय देना है, जिसे आप खेतों आदि में नहीं डाल सकते। * और जब यह कार्यक्षेत्र से बाहर हो जाता है तो स्वतः ही निपट जाएगा। जो वास्तव में C ++ / CLI करता है। C ++ / CLI में, आप इस तरह कोड लिखते हैं:

void f()
{
    Foo f1;
    Foo^ f2 = gcnew Foo();
    Foo::someStaticField = f2;

    // f1 is disposed here, the object pointed to by f2 isn't
}

यह मूल रूप से निम्नलिखित IL # के समान ही IL को संकलित करता है:

void f()
{
    using (Foo f1 = new Foo())
    {
        Foo f2 = new Foo();
        Foo.someStaticField = f2;
    }
    // f1 is disposed here, the object pointed to by f2 isn't
}

निष्कर्ष निकालने के लिए, अगर मुझे यह अनुमान लगाना था कि C # के डिजाइनरों ने RAII क्यों नहीं जोड़ा, तो इसका कारण यह है कि उन्होंने सोचा कि दो अलग-अलग प्रकार के स्थानीय चर इसके लायक नहीं हैं, ज्यादातर क्योंकि GC के साथ एक भाषा में, निर्धारक अंतिमकरण उपयोगी नहीं है जो अक्सर।

* ऑपरेटर के समकक्ष के बिना नहीं &, जो C ++ / CLI में है %। हालांकि ऐसा करना इस अर्थ में "असुरक्षित" है कि विधि समाप्त होने के बाद, क्षेत्र एक निपटाए गए ऑब्जेक्ट को संदर्भित करेगा।


1
C # तुच्छ रूप से RAII कर सकता है यदि यह structD जैसे प्रकारों के लिए विनाशकों की अनुमति देता है।
जान हुदेक

6

यदि आप usingब्लॉकों से परेशान हैं, तो वे अपने गवाह हैं, शायद हम सी # कल्पना को बदलने के बजाय, कम खोजकर्ता की ओर एक छोटा बच्चा-कदम उठा सकते हैं। इस कोड पर विचार करें:

public void ReadFile ()
{
  string filename = "myFile.dat";
  local Stream file = File.Open(filename);
  file.Read(blah blah blah);
}

localमेरे द्वारा जोड़ा गया कीवर्ड देखें ? यह सब कुछ थोड़ा और अधिक जोड़-तोड़ करने वाला चीनी है, जैसे usingकि कंपाइलर को वेरिएबल के दायरे के अंत Disposeमें एक finallyब्लॉक में कॉल करना । बस इतना ही। यह पूरी तरह से इसके बराबर है:

public void ReadFile ()
{
  string filename = "myFile.dat";
  using (Stream file = File.Open(filename))
  {
      file.Read(blah blah blah);
  }
}

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

यहाँ हार्ड-टू-रिज़ॉल्यूशन स्कोप वाले मुद्दे हो सकते हैं, हालाँकि मैं इसे अभी नहीं देख सकता हूँ, और जो कोई भी इसे पा सकता है मैं उसकी सराहना करूँगा।


1
@ mike30 लेकिन इसे टाइप डेफिनिशन में ले जाने से आप दूसरों को सूचीबद्ध समस्याओं के बिल्कुल सामने आ जाते हैं - यदि आप पॉइंटर को एक अलग विधि से पास करते हैं या फ़ंक्शन से वापस आते हैं तो क्या होता है? इस तरह स्कोपिंग को दायरे में घोषित किया जाता है, अन्यत्र नहीं। एक प्रकार डिस्पोजेबल हो सकता है, लेकिन यह डिस्पोज को कॉल करने के लिए नहीं है।
अवनेर शाहर-कश्तन

3
@ mike30: मेह। यह सब वाक्यविन्यास ब्रेसिज़ को हटा देता है और, विस्तार से, स्कूपिंग नियंत्रण जो वे प्रदान करते हैं।
रॉबर्ट हार्वे

1
@RobertHarvey बिल्कुल। यह क्लीनर, कम नेस्टेड कोड के लिए कुछ लचीलेपन का त्याग करता है। यदि हम @ डेलान के सुझाव को लेते हैं और usingकीवर्ड का पुन: उपयोग करते हैं , तो हम मौजूदा व्यवहार को बनाए रख सकते हैं और इसका उपयोग भी कर सकते हैं, उन मामलों के लिए जहां हमें विशिष्ट दायरे की आवश्यकता नहीं है। usingवर्तमान दायरे में ब्रेस-कम डिफ़ॉल्ट है।
अवनेर शाहर-कश्तन

1
मुझे भाषा डिजाइन में अर्ध-व्यावहारिक अभ्यासों से कोई समस्या नहीं है।
अवनर शाहर-कश्तन

1
@RobertHarvey। आपको ऐसा लगता है कि वर्तमान में C # में लागू किसी भी चीज़ के खिलाफ पूर्वाग्रह नहीं है। अगर हम C # 1.0 से संतुष्ट नहीं होते, तो हमारे पास जेनेरिक, लिनीक, उपयोग-ब्लॉक, ipmlicit प्रकार आदि नहीं होते। यह वाक्यविन्यास निहितार्थ के मुद्दे को हल नहीं करता है, लेकिन वर्तमान दायरे में बांधने के लिए यह एक अच्छी चीनी है।
माइक 30

1

RAII कचरा-एकत्रित भाषा में कैसे काम करता है, इसके उदाहरण के लिए, withPython में कीवर्ड की जाँच करें । नियतांत्रिक रूप से नष्ट की गई वस्तुओं पर निर्भर होने के बजाय, आइए आपको एक दिए गए शाब्दिक दायरे के सहयोगी __enter__()और __exit__()तरीके बताते हैं। एक सामान्य उदाहरण है:

with open('output.txt', 'w') as f:
    f.write('Hi there!')

C ++ की RAII शैली के साथ, फ़ाइल को उस ब्लॉक से बाहर निकलते समय बंद कर दिया जाएगा, चाहे वह 'सामान्य' निकास से बाहर हो, कोई भी break, तत्काल returnया अपवाद नहीं।

ध्यान दें कि open()कॉल सामान्य फ़ाइल खोलने का कार्य है। इस कार्य को करने के लिए, दी गई फ़ाइल ऑब्जेक्ट में दो विधियाँ शामिल हैं:

def __enter__(self):
  return self
def __exit__(self):
  self.close()

यह पायथन में एक सामान्य मुहावरा है: जो वस्तुएं संसाधन से जुड़ी होती हैं, उनमें आमतौर पर ये दो विधियां शामिल होती हैं।

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


7
withपायथन में लगभग using# सी की तरह है, और जहां तक ​​यह सवाल नहीं है तो RAII।

1
पायथन का "के साथ" स्कोप-बाउंड संसाधन प्रबंधन है, लेकिन यह एक स्मार्ट-पॉइंटर के निहितार्थ को याद कर रहा है। सूचक को स्मार्ट घोषित करने के कार्य को "स्पष्ट" माना जा सकता है, लेकिन यदि संकलक वस्तुओं के भाग के रूप में स्मार्टनेस लागू करता है, तो यह "निहित" की ओर झुक जाएगा।
mike30

AFAICT, RAII का बिंदु संसाधनों के लिए सख्त ढलान की स्थापना कर रहा है। यदि आप केवल वस्तुओं को निपटाकर किए जाने में रुचि रखते हैं, तो नहीं, कचरा एकत्र करने वाली भाषाएं ऐसा नहीं कर सकती हैं। यदि आप लगातार संसाधनों को जारी करने में रुचि रखते हैं, तो यह इसे करने का एक तरीका है (दूसरा deferगो भाषा में है)।
जेवियर

1
वास्तव में, मुझे लगता है कि यह कहना उचित है कि जावा और सी # स्पष्ट निर्माण का पक्ष लेते हैं। अन्यथा, इंटरफेसेस और इनहेरिटेंस का उपयोग करने में निहित सभी समारोह से परेशान क्यों हैं?
रॉबर्ट हार्वे

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