कंस्ट्रक्टर में या घोषणा के आधार पर प्रारंभिक क्षेत्र?


413

मैं हाल ही में C # और Java में प्रोग्रामिंग कर रहा हूं और मैं उत्सुक हूं कि सबसे अच्छी जगह मेरी क्लास फील्ड्स को इनिशियलाइज़ करना है।

क्या मुझे यह घोषणा में करना चाहिए? "

public class Dice
{
    private int topFace = 1;
    private Random myRand = new Random();

    public void Roll()
    {
       // ......
    }
}

या एक कंस्ट्रक्टर में:

public class Dice
{
    private int topFace;
    private Random myRand;

    public Dice()
    {
        topFace = 1;
        myRand = new Random();
    }

    public void Roll()
    {
        // .....
    }
}

मैं वास्तव में उत्सुक हूं कि आप में से कुछ दिग्गजों को क्या लगता है कि यह सबसे अच्छा अभ्यास है। मैं सुसंगत रहना चाहता हूं और एक दृष्टिकोण से रहना चाहता हूं।


3
ध्यान दें कि संरचना के लिए आपके पास फ़ील्ड इनिशियलाइज़र नहीं हो सकते हैं, इसलिए आपके पास कंस्ट्रक्टर का उपयोग करने के अलावा कोई विकल्प नहीं है।
योयो

जब से आप "सर्वश्रेष्ठ अभ्यास" लाए हैं: satisfice.com/blog/archives/5164 , forbes.com/sites/mikemyatt/2012/08/15/best-practices-arent/…
स्टीफन C

जवाबों:


310

मेरे नियम:

  1. घोषणा में मूलभूत मूल्यों के साथ प्रारंभ न करें ( null, false, 0, 0.0...)।
  2. यदि आपके पास कोई ऐसा पैरामीटर पैरामीटर नहीं है जो फ़ील्ड के मान को बदलता है तो घोषणा में प्रारंभिकता को प्राथमिकता दें।
  3. यदि एक निर्माता पैरामीटर के कारण फ़ील्ड का मान बदलता है, तो निर्माणकर्ताओं में आरंभीकरण होता है।
  4. अपने अभ्यास (सबसे महत्वपूर्ण नियम) में सुसंगत रहें।

4
मुझे उम्मीद है कि कोकोस का मतलब है कि आपको सदस्यों को उनके डिफ़ॉल्ट मानों (0, झूठे, अशक्त आदि) से इनिशियलाइज़ नहीं करना चाहिए, क्योंकि कंपाइलर आपके लिए (1.) ऐसा करेगा। लेकिन यदि आप किसी फ़ील्ड को उसके डिफ़ॉल्ट मान के अलावा किसी अन्य चीज़ से प्रारंभ करना चाहते हैं, तो आपको इसे घोषणा (2.) में करना चाहिए। मुझे लगता है कि यह "डिफ़ॉल्ट" शब्द का उपयोग हो सकता है जो आपको भ्रमित करता है।
रिकी हेल्गेसन

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

32
किसी प्रकार default(T)का डिफ़ॉल्ट मान हमेशा वह मान होता है जिसका आंतरिक बाइनरी प्रतिनिधित्व होता है 0
ओलिवियर जैकोट-डेसकोम्बर्स

15
आप नियम 1 को पसंद करते हैं या नहीं, इसका उपयोग आसानी से फ़ील्ड के साथ नहीं किया जा सकता है, जिसे कंस्ट्रक्टर के समाप्त होने के समय तक स्पष्ट रूप से प्रारंभ किया जाना चाहिए।
योयो

36
मैं उन लोगों से असहमत हूं जो नियम से असहमत हैं। C # भाषा सीखने के लिए दूसरों से अपेक्षा करना ठीक है। जैसे हम प्रत्येक foreachलूप को "इस सूची में सभी वस्तुओं के लिए निम्नलिखित को दोहराते हैं" के साथ टिप्पणी नहीं करते हैं , हमें सी # डिफ़ॉल्ट मानों को लगातार आराम करने की आवश्यकता नहीं है। हमें यह भी दिखावा करने की आवश्यकता नहीं है कि C # में अनैतिक रूप से शब्दार्थ है। चूंकि मूल्य के अभाव का एक स्पष्ट अर्थ है, इसलिए इसे छोड़ना ठीक है। यदि यह स्पष्ट होना आदर्श है, तो आपको हमेशा newनए प्रतिनिधि बनाते समय उपयोग करना चाहिए (जैसा कि C # 1 में आवश्यक था)। लेकिन कौन करता है? भाषा ईमानदार कोडर्स के लिए डिज़ाइन की गई है।
एडवर्ड ब्रे

149

C # में यह कोई फर्क नहीं पड़ता। आपके द्वारा दिए गए दो कोड नमूने पूरी तरह से बराबर हैं। पहले उदाहरण में C # कंपाइलर (या यह CLR है?) एक खाली कंस्ट्रक्टर का निर्माण करेगा और वैरिएबल को इनिशियलाइज़ करेगा जैसे कि वे कंस्ट्रक्टर में थे (इस बारे में थोड़ी बारीकियाँ हैं कि जॉन स्कीट नीचे टिप्पणी में बताते हैं)। यदि पहले से ही एक कंस्ट्रक्टर है, तो किसी भी "ऊपर" को प्रारंभिक रूप से ऊपर ले जाया जाएगा।

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


4
यदि आप GetUninitializedObject के साथ वर्ग को इनिशियलाइज़ करना चुनते हैं तो यह सही नहीं है। जो कुछ भी ctor में है उसे छुआ नहीं जाएगा बल्कि क्षेत्र घोषणाओं को चलाया जाएगा।
वुल्फ ५

28
वास्तव में यह मायने रखता है। यदि बेस क्लास कंस्ट्रक्टर एक वर्चुअल मेथड कहता है (जो आमतौर पर एक बुरा विचार है, लेकिन ऐसा हो सकता है) जो व्युत्पन्न क्लास में ओवरराइड होता है, तो उदाहरण वेरिएबल इनिशियलाइज़र का उपयोग करते हुए वैरिएबल को इनीशियलाइज़ किया जाएगा जब मेथड को बुलाया जाता है - जबकि क्लास में इनिशियलाइज़ेशन का उपयोग करते हुए निर्माता, वे नहीं होंगे। (इंस्टेंस वैरिएबल इनिशियलाइज़र को बेस क्लास कंस्ट्रक्टर कहे जाने से पहले निष्पादित किया जाता है।)
जॉन स्कीट

2
वास्तव में? मैं कसम खाता हूं कि मैंने रिक्टर के सीएलआर से C # (दूसरा संस्करण जो मुझे लगता है) के माध्यम से इस जानकारी को पकड़ा था और इसका सार यह था कि यह सिंटैक्टिक शुगर था (मैंने इसे गलत तरीके से पढ़ा हो सकता है?) और सीएलआर ने बस कंस्ट्रक्टर में वेरिएबल को जाम कर दिया। लेकिन आप यह कह रहे हैं कि यह मामला नहीं है, यानी आधार कैपिटल में वर्चुअल कॉल करने के क्रेज्ड परिदृश्य में ctor इनिशियलाइजेशन से पहले मेंबर आरंभीकरण फायर कर सकता है और प्रश्न में क्लास में ओवरराइड कर सकता है। क्या मैं सही से समझ पाया? क्या आपको यह पता चला? 5 साल पुरानी पोस्ट पर इस अप-टू-डेट टिप्पणी के रूप में हैरान (OMG यह 5 साल हो गया है?)।
Quibblesome

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

1
@Quibblesome, पूर्व में एक समस्या होगी, यदि मान असाइन किया गया है और WCF पद्धति में दिया गया है। जो डेटा को मॉडल के डिफ़ॉल्ट डेटा प्रकार पर रीसेट कर देगा। सबसे अच्छा अभ्यास निर्माणकर्ता (बाद में एक) में आरंभीकरण करना है।
प्राणेश जनार्दन

16

C # का शब्दार्थ जावा से थोड़ा भिन्न है। सुपरक्लास कंस्ट्रक्टर को कॉल करने से पहले घोषणा में C # असाइनमेंट किया जाता है। जावा में यह तुरंत किया जाता है जिसके बाद 'इस' का उपयोग करने की अनुमति देता है (विशेष रूप से अनाम आंतरिक वर्गों के लिए उपयोगी), और इसका मतलब है कि दो रूपों के शब्दार्थ वास्तव में मेल खाते हैं।

यदि आप कर सकते हैं, तो खेतों को अंतिम बनाएं।


15

मुझे लगता है कि वहाँ एक चेतावनी है। मैंने एक बार इस तरह की त्रुटि की थी: एक व्युत्पन्न वर्ग के अंदर, मैंने "अमूर्त घोषणा पर आरंभ" करने की कोशिश की, जो कि एक अमूर्त वर्ग से विरासत में मिला है। इसका नतीजा यह था कि खेतों के दो सेट मौजूद थे, एक "बेस" है और दूसरा नव घोषित है, और यह मुझे डिबग करने में कुछ समय लगा।

सबक: विरासत में मिले खेतों को शुरू करने के लिए , आप इसे कंस्ट्रक्टर के अंदर करेंगे।


इसलिए यदि आप क्षेत्र को संदर्भित करते हैं derivedObject.InheritedField, तो क्या यह आपके आधार एक या व्युत्पन्न एक की बात कर रहा है?
रायलू

6

अपने उदाहरण में प्रकार को मानते हुए, निश्चित रूप से निर्माणकर्ता में फ़ील्ड को आरम्भ करना पसंद करते हैं। असाधारण मामले हैं:

  • स्थिर वर्गों / विधियों में फ़ील्ड
  • स्थिर / अंतिम / एट अल के रूप में टाइप किए गए फ़ील्ड

मैं हमेशा सामग्री की तालिका के रूप में एक वर्ग के शीर्ष पर फ़ील्ड सूचीकरण के बारे में सोचता हूं (इसमें क्या निहित है, इसका उपयोग कैसे किया जाता है), और परिचय के रूप में निर्माणकर्ता। पाठ्यक्रम के तरीके अध्याय हैं।


ऐसा "निश्चित" क्यों है? आपने इसके तर्क को स्पष्ट किए बिना केवल एक शैली वरीयता प्रदान की। ओह इंतजार, कभी मन नहीं है, @quibblesome के जवाब के अनुसार , वे "पूरी तरह से समकक्ष" हैं, इसलिए यह वास्तव में सिर्फ एक व्यक्तिगत शैली की प्राथमिकता है।
रायलू

4

क्या होगा अगर मैंने आपको बताया, यह निर्भर करता है?

मैं सामान्य रूप से सब कुछ शुरू करता हूं और इसे सुसंगत तरीके से करता हूं। हां यह अति स्पष्ट है लेकिन इसे बनाए रखना थोड़ा आसान है।

अगर हम प्रदर्शन के बारे में चिंतित हैं, तो ठीक है, मैं केवल वही करता हूं जो किया जाना चाहिए और इसे उन क्षेत्रों में रखें जहां यह हिरन के लिए सबसे अधिक धमाके देता है।

एक वास्तविक समय प्रणाली में, मैं सवाल करता हूं कि क्या मुझे भी चर या स्थिरांक की आवश्यकता है।

और सी ++ में मैं अक्सर किसी भी जगह पर कोई आरंभीकरण के बगल में नहीं करता हूं और इसे एक इनिट () फ़ंक्शन में स्थानांतरित करता हूं। क्यों? ठीक है, C ++ में अगर आप किसी ऐसी चीज को इनिशियलाइज़ कर रहे हैं जो ऑब्जेक्ट कंस्ट्रक्शन के दौरान अपवाद छोड़ सकती है तो आप खुद को मेमोरी लीक्स में खोल सकते हैं।


4

कई और विभिन्न परिस्थितियां हैं।

मुझे बस एक खाली सूची चाहिए

स्थिति स्पष्ट है। मुझे बस अपनी सूची तैयार करने की आवश्यकता है और जब कोई व्यक्ति सूची में किसी आइटम को जोड़ता है तो अपवाद को फेंकने से रोकता है।

public class CsvFile
{
    private List<CsvRow> lines = new List<CsvRow>();

    public CsvFile()
    {
    }
}

मैं मूल्यों को जानता हूं

मुझे वास्तव में पता है कि मुझे डिफ़ॉल्ट रूप से क्या मूल्य चाहिए या मुझे कुछ अन्य तर्क का उपयोग करने की आवश्यकता है।

public class AdminTeam
{
    private List<string> usernames;

    public AdminTeam()
    {
         usernames = new List<string>() {"usernameA", "usernameB"};
    }
}

या

public class AdminTeam
{
    private List<string> usernames;

    public AdminTeam()
    {
         usernames = GetDefaultUsers(2);
    }
}

संभावित मूल्यों के साथ खाली सूची

कभी-कभी मैं दूसरे कंस्ट्रक्टर के माध्यम से मूल्यों को जोड़ने की संभावना के साथ डिफ़ॉल्ट रूप से एक खाली सूची की उम्मीद करता हूं।

public class AdminTeam
{
    private List<string> usernames = new List<string>();

    public AdminTeam()
    {
    }

    public AdminTeam(List<string> admins)
    {
         admins.ForEach(x => usernames.Add(x));
    }
}

3

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


3

C # का डिज़ाइन बताता है कि इनलाइन इनिशियलाइज़ेशन को प्राथमिकता दी जाती है, या यह भाषा में नहीं होगा। किसी भी समय आप कोड में विभिन्न स्थानों के बीच क्रॉस-रेफरेंस से बच सकते हैं, आप आमतौर पर बेहतर होते हैं।

स्टैटिक फील्ड इनिशियलाइज़ेशन के साथ संगति की बात भी है, जिसे सर्वश्रेष्ठ प्रदर्शन के लिए इनलाइन होना चाहिए। कंस्ट्रक्टर डिज़ाइन के लिए रूपरेखा डिज़ाइन दिशानिर्देश यह कहते हैं:

, स्पष्ट रूप से स्थिर निर्माणकर्ताओं का उपयोग करने के बजाय स्थिर फ़ील्ड इनलाइन को प्रारंभ करना, क्योंकि रनटाइम उन प्रकारों के प्रदर्शन को अनुकूलित करने में सक्षम है जिनके पास स्पष्ट रूप से परिभाषित स्थिर निर्माता नहीं है।

इस संदर्भ में "विचार करें" का अर्थ है ऐसा करना जब तक कि कोई अच्छा कारण न हो। स्टैटिक इनिशियलाइज़र फ़ील्ड्स के मामले में, एक अच्छा कारण यह होगा कि यदि इनिशियलाइज़ेशन बहुत जटिल है, तो कोड इनलाइन होना चाहिए।


2

सुसंगत होना महत्वपूर्ण है, लेकिन यह सवाल खुद से पूछना है: "क्या मेरे पास किसी और चीज के लिए एक निर्माता है?"

आमतौर पर, मैं डेटा ट्रांसफ़र के लिए मॉडल बना रहा हूं कि वर्ग स्वयं चर के लिए आवास के रूप में काम के अलावा कुछ भी नहीं करता है।

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

तो जैसा कि कई अन्य लोगों ने कहा है, यह आपके उपयोग पर निर्भर करता है। इसे सरल रखें, और कुछ भी अतिरिक्त न करें जो आपके पास नहीं है।


2

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


1

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


2
C # में, फ़ील्ड को हमेशा पहले डिफ़ॉल्ट मान सेट किया जाता है। एक शुरुआती की उपस्थिति से कोई फर्क नहीं पड़ता।
जेफरी एल व्हिटलेज

0

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

यदि आप एक ऐसे उदाहरण चर पर असाइन करने जा रहे हैं, जो किसी भी पैरामीटर से प्रभावित नहीं होता है जिसे आप अपने कंस्ट्रक्टर के पास भेजने जा रहे हैं, तो उसे घोषणा के समय पर असाइन करें।


0

सर्वोत्तम अभ्यास के बारे में आपके प्रश्न का सीधा उत्तर नहीं है, लेकिन एक महत्वपूर्ण और संबंधित रिफ्रेशर बिंदु यह है कि सामान्य श्रेणी की परिभाषा के मामले में, या तो इसे डिफ़ॉल्ट मानों के साथ आरंभ करने के लिए संकलक पर छोड़ दें या हमें खेतों को आरम्भ करने के लिए एक विशेष विधि का उपयोग करना होगा उनके डिफ़ॉल्ट मानों के लिए (यदि कोड पठनीयता के लिए पूर्ण आवश्यक है)।

class MyGeneric<T>
{
    T data;
    //T data = ""; // <-- ERROR
    //T data = 0; // <-- ERROR
    //T data = null; // <-- ERROR        

    public MyGeneric()
    {
        // All of the above errors would be errors here in constructor as well
    }
}

और जेनेरिक फ़ील्ड को उसके डिफ़ॉल्ट मान को इनिशियलाइज़ करने की विशेष विधि निम्नलिखित है:

class MyGeneric<T>
{
    T data = default(T);

    public MyGeneric()
    {           
        // The same method can be used here in constructor
    }
}

0

जब आपको कुछ तर्क या त्रुटि से निपटने की आवश्यकता नहीं होती है:

  • घोषणा के आधार पर प्रारंभिक क्षेत्र

जब आपको कुछ तर्क या त्रुटि से निपटने की आवश्यकता होती है:

  • कंस्ट्रक्टर में प्रारंभिक फ़ील्ड फ़ील्ड

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

से https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html

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