निरर्थक डिफ़ॉल्ट मान के साथ संरचना


12

मेरे सिस्टम में मैं अक्सर एयरपोर्ट कोड ( "YYZ"और "LAX", "SFO"आदि) के साथ काम करता हूं , वे हमेशा एक ही प्रारूप (3 अक्षर, अपरकेस के रूप में प्रतिनिधित्व) में होते हैं। प्रणाली आमतौर पर इनमें से 25-50 (अलग-अलग) कोड प्रति एपीआई अनुरोध से निपटती है, कुल मिलाकर एक हजार से अधिक आवंटन के साथ, वे हमारे आवेदन की कई परतों से गुजरते हैं, और समानता के लिए तुलना की जाती है।

हमने बस पासिंग स्ट्रिंग्स के साथ शुरुआत की, जो थोड़ा ठीक काम करता था, लेकिन हमने जल्दी से एक गलत कोड में कई प्रोग्रामिंग गलतियों को देखा, जहां 3 अंकों का कोड अपेक्षित था। हम उन मुद्दों पर भी भागे, जहाँ हम केस-असंवेदनशील तुलना करने वाले थे और इसके परिणामस्वरूप बग नहीं बने।

इससे, मैंने चारों ओर से गुजरने वाले तारों को रोकने और एक Airportवर्ग बनाने का फैसला किया , जिसमें एक एकल निर्माता है जो हवाई अड्डे के कोड को लेता है और मान्य करता है।

public sealed class Airport
{
    public Airport(string code)
    {
        if (code == null)
        {
            throw new ArgumentNullException(nameof(code));
        }

        if (code.Length != 3 || !char.IsLetter(code[0]) 
        || !char.IsLetter(code[1]) || !char.IsLetter(code[2]))
        {
            throw new ArgumentException(
                "Must be a 3 letter airport code.", 
                nameof(code));
        }

        Code = code.ToUpperInvariant();
    }

    public string Code { get; }

    public override string ToString()
    {
        return Code;
    }

    private bool Equals(Airport other)
    {
        return string.Equals(Code, other.Code);
    }

    public override bool Equals(object obj)
    {
        return obj is Airport airport && Equals(airport);
    }

    public override int GetHashCode()
    {
        return Code?.GetHashCode() ?? 0;
    }

    public static bool operator ==(Airport left, Airport right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(Airport left, Airport right)
    {
        return !Equals(left, right);
    }
}

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

हालाँकि, मैंने जो नोटिस किया था, वह यह था कि कचरा संग्रह बहुत अधिक बार चल रहा था, जिसे मैंने Airportएकत्रित करने के बहुत सारे उदाहरणों पर नज़र रखी ।

इसका मेरा हल classएक में परिवर्तित करना था struct। अधिकतर यह एक खोजशब्द परिवर्तन था, इसके अपवाद के साथ GetHashCodeऔर ToString:

public override string ToString()
{
    return Code ?? string.Empty;
}

public override int GetHashCode()
{
    return Code?.GetHashCode() ?? 0;
}

जहां default(Airport)उपयोग किया जाता है उस मामले को संभालने के लिए ।

मेरे सवाल:

  1. क्या Airportसामान्य रूप से एक वर्ग या संरचना एक अच्छा समाधान बना रहा था, या क्या मैं गलत समस्या को हल कर रहा हूं / इसे गलत तरीके से हल कर रहा हूं? यदि यह एक अच्छा समाधान नहीं है, तो बेहतर उपाय क्या है?

  2. जहां default(Airport)उपयोग किया जाता है वहां मेरे एप्लिकेशन को इंस्टेंस को कैसे संभालना चाहिए ? default(Airport)मेरे आवेदन के लिए एक प्रकार का निरर्थक है, इसलिए मैं उन if (airport == default(Airport) { throw ... }जगहों पर कर रहा हूं जहां Airport(और इसकी Codeसंपत्ति) का एक उदाहरण ऑपरेशन के लिए महत्वपूर्ण है।

नोट: मैंने C # / VB संरचना के प्रश्नों की समीक्षा की - शून्य डिफ़ॉल्ट मान वाले मामले से कैसे बचें, जो दी गई संरचना के लिए अमान्य माना जाता है? , और मेरे प्रश्न पूछने से पहले संरचना का उपयोग करें या नहीं , लेकिन मुझे लगता है कि मेरे प्रश्न अपने स्वयं के पद को वारंट करने के लिए पर्याप्त हैं।


7
क्या कचरा संग्रह आपके आवेदन के प्रदर्शन के तरीके पर एक सामग्री प्रभाव डालता है? दूसरे शब्दों में, क्या इससे कोई फर्क पड़ता है?
रॉबर्ट हार्वे

वैसे भी, हाँ, वर्ग समाधान एक "अच्छा" था। जिस तरह से आप जानते हैं कि यह आपकी समस्या का समाधान बिना किसी नए को बनाए है।
रॉबर्ट हार्वे

2
एक तरह से आप default(Airport)समस्या को हल कर सकते हैं, बस डिफ़ॉल्ट इंस्टेंस को रोकना है। आप ऐसा कर सकते हैं कि एक पैरामीटर रहित कंस्ट्रक्टर लिखकर और उसमें InvalidOperationExceptionया फेंककर NotImplementedException
रॉबर्ट हार्वे

3
एक साइड नोट पर, यह पुष्टि करने के बजाय कि आपका इनिशियलाइज़ेशन स्ट्रिंग वास्तव में 3 अल्फा वर्णों का है, क्यों न इसे सभी हवाई अड्डा कोडों की परिमित सूची (जैसे, github.com/datasets/airport-codes या समान) से तुलना करें?
डेन पीचेलमैन

2
मैं कई बियर को शर्त लगाने को तैयार हूं कि यह प्रदर्शन समस्या की जड़ नहीं है। एक सामान्य लैपटॉप ऑर्डर 10 एम ऑब्जेक्ट / सेकंड में आवंटित कर सकता है।
एसेन स्कोव पेडरसेन

जवाबों:


6

अपडेट: मैंने C # स्ट्रक्चर्स के बारे में कुछ गलत धारणाओं को संबोधित करने के लिए अपना जवाब फिर से लिखा है, साथ ही ओपी ने हमें टिप्पणियों में सूचित किया है कि आंतरिक स्ट्रिंग्स का उपयोग किया जा रहा है।


यदि आप अपने सिस्टम में आने वाले डेटा को नियंत्रित कर सकते हैं, तो अपने प्रश्न में पोस्ट किए गए वर्ग का उपयोग करें। यदि कोई चलाता है तो default(Airport)उन्हें एक nullमूल्य वापस मिलेगा । Equalsजब भी अशक्त हवाई अड्डे की वस्तुओं की तुलना में झूठी वापसी करने के लिए अपनी निजी पद्धति लिखना सुनिश्चित करें , और फिर NullReferenceExceptionकोड में कहीं और उड़ने दें ।

हालाँकि, यदि आप सिस्टम में उन स्रोतों से डेटा ले रहे हैं जिन्हें आप नियंत्रित नहीं करते हैं, तो आप पूरे धागे को क्रैश करना नहीं चाहते हैं। इस मामले में एक संरचना सरल तथ्य के लिए आदर्श है जो default(Airport)आपको nullसूचक के अलावा कुछ और देगा । "कोई मूल्य नहीं" या "डिफ़ॉल्ट मान" का प्रतिनिधित्व करने के लिए एक स्पष्ट मूल्य बनाएं ताकि आपके पास स्क्रीन पर या लॉग फ़ाइल में प्रिंट करने के लिए कुछ हो (जैसे "---" उदाहरण के लिए)। वास्तव में, मैं बस codeनिजी रखूंगा और किसी Codeसंपत्ति का खुलासा नहीं करूंगा - बस यहां व्यवहार पर ध्यान केंद्रित करें।

public struct Airport
{
    private string code;

    public Airport(string code)
    {
        // Check `code` for validity, throw exceptions if not valid

        this.code = code;
    }

    public override string ToString()
    {
        return code ?? (code = "---");
    }

    // int GetHashcode()

    // bool Equals(...)

    // bool operator ==(...)

    // bool operator !=(...)

    private bool Equals(Airport other)
    {
        if (other == null)
            // Even if this method is private, guard against null pointers
            return false;

        if (ToString() == "---" || other.ToString() == "---")
            // "Default" values should never match anything, even themselves
            return false;

        // Do a case insensitive comparison to enforce logic that airport
        // codes are not case sensitive
        return string.Equals(
            ToString(),
            other.ToString(),
            StringComparison.InvariantCultureIgnoreCase);
    }
}

अन्य default(Airport)स्ट्रिंग "---"एयरपोर्ट कोड्स की तुलना में बदतर स्थिति परिदृश्य स्ट्रिंग स्ट्रिंग में परिवर्तित हो जाता है और गलत हो जाता है। कोई भी "डिफ़ॉल्ट" एयरपोर्ट कोड अन्य डिफ़ॉल्ट एयरपोर्ट कोड सहित कुछ भी मेल नहीं खाता है।

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

मैं नियमों को थोड़ा यहाँ झुकता हूँ, इस वजह से।


मूल उत्तर (कुछ तथ्यात्मक त्रुटियों के साथ)

यदि आप अपने सिस्टम में आने वाले डेटा को नियंत्रित कर सकते हैं, तो मैं रॉबर्ट हार्वे की टिप्पणियों में सुझाए गए अनुसार करूंगा: एक पैरामीटर रहित निर्माणकर्ता बनाएं और जब इसे बुलाया जाए तो एक अपवाद फेंक दें। यह अमान्य डेटा को सिस्टम में प्रवेश करने से रोकता है default(Airport)

public Airport()
{
    throw new InvalidOperationException("...");
}

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

public Airport()
{
    Code = "---";
}

चूंकि आप stringकोड के रूप में उपयोग कर रहे हैं , इसलिए संरचना का उपयोग करने का कोई मतलब नहीं है। स्टैक पर संरचना आवंटित की जाती है, केवल Codeढेर मेमोरी में स्ट्रिंग के लिए एक संकेतक के रूप में आवंटित किया जाता है, इसलिए यहां कक्षा और संरचना के बीच कोई अंतर नहीं है।

यदि आपने हवाई अड्डे के कोड को चार आइटम सरणी में बदल दिया है तो स्टैक पर एक संरचना पूरी तरह से आवंटित की जाएगी। फिर भी डेटा का आयतन इतना बड़ा नहीं है कि कोई फर्क पड़े।


अगर मेरा आवेदन Codeसंपत्ति के लिए नजरबंद तार का उपयोग कर रहा था , तो क्या यह आपकी बात को सही ठहराएगा, जो कि स्ट्रिंग मेमोरी में आपकी बात के बारे में है?
मैथ्यू

@ मैथ्यू: क्या आप प्रदर्शन की समस्या देने वाले वर्ग का उपयोग कर रहे हैं? यदि नहीं, तो एक सिक्का फ्लिप करके तय करें कि किसका उपयोग करना है।
ग्रेग बरगार्ड

4
@ मैथ्यू: वास्तव में महत्वपूर्ण बात यह है कि आप कोड्स और तुलनाओं को सामान्य करने के परेशानी वाले तर्क को केंद्रीकृत करते हैं। उसके बाद "क्लास बनाम स्ट्रक्चर" सिर्फ एक अकादमिक चर्चा है, जब तक कि आप एक अकादमिक चर्चा करने के लिए अतिरिक्त डेवलपर समय का औचित्य साबित करने के लिए प्रदर्शन में एक बड़े पर्याप्त प्रभाव को मापते हैं।
ग्रेग बरगार्ड

1
यह सच है, मुझे समय-समय पर अकादमिक चर्चा करने में कोई आपत्ति नहीं है अगर यह मुझे भविष्य में बेहतर सूचित समाधान बनाने में मदद करता है।
मैथ्यू

@ मैथ्यू: हां, आप बिल्कुल सही हैं। वे कहते हैं "बात सस्ती है।" यह निश्चित रूप से बात नहीं करने और कुछ खराब निर्माण से सस्ता है। :)
ग्रेग बरगार्ड

13

फ्लाईवेट पैटर्न का उपयोग करें

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

एक अतिरिक्त मामूली लाभ (कम से कम जावा में, सी # के बारे में निश्चित नहीं है) यह है कि आपको एक equals()विधि लिखने की आवश्यकता नहीं है , एक साधारण काम ==करेगा। उसी के लिए hashcode()


3
फ्लाईवेट पैटर्न का उत्कृष्ट उपयोग।
नील

2
यह मानते हुए कि ओपी एक संरचना का उपयोग करता है और एक वर्ग का नहीं, क्या स्ट्रिंग इंटर्न पहले से ही पुन: प्रयोज्य स्ट्रिंग मानों को नहीं संभाल रहा है? स्टैक पर संरचनाएं पहले से ही रहती हैं, तार पहले से ही पुन: उपयोग किए जा रहे हैं ताकि स्मृति में डुप्लिकेट मान से बचा जा सके। फ्लाईवेट पैटर्न से क्या अतिरिक्त लाभ प्राप्त होगा?
फ्लाटर

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

@ फ़्लैटर रीज़नेबल पॉइंट। मैं कहता हूं कि जूनियर प्रोग्रामर को स्टैक बनाम हीप के बारे में कम आवश्यकता है। इसके अलावा मेरे अलावा देखें - बराबर लिखने की कोई ज़रूरत नहीं है ()।
user949300

1
@Greg बर्गहार्ट यदि getAirportOrCreate()कोड ठीक से समन्‍वयित है, तो कोई तकनीकी कारण नहीं है कि आप रनटाइम के दौरान आवश्यकतानुसार नए हवाई अड्डे नहीं बना सकते हैं। व्यावसायिक कारण हो सकते हैं।
15:

3

मैं एक विशेष रूप से उन्नत प्रोग्रामर नहीं हूँ, लेकिन यह एक Enum के लिए एक सही उपयोग नहीं होगा?

सूची या स्ट्रिंग्स से एनम कक्षाओं के निर्माण के विभिन्न तरीके हैं। यहाँ एक है जो मैंने अतीत में देखा है, निश्चित नहीं है कि यह सबसे अच्छा तरीका है।

https://blog.kloud.com.au/2016/06/17/converting-webconfig-values-into-enum-or-list/


2
जब संभावित रूप से हजारों अलग-अलग मूल्य होते हैं (जैसा कि एयरपोर्ट कोड के साथ होता है), तो एक एनम व्यावहारिक नहीं है।
बेन कॉटरेल

हां, लेकिन मैंने जो लिंक पोस्ट किया है वह यह है कि स्ट्रिंग्स को एनम के रूप में कैसे लोड किया जाए। यहां एक लुकअप टेबल को एनम के रूप में लोड करने के लिए एक और लिंक दिया गया है। यह थोड़ा काम का हो सकता है, लेकिन दुश्मनी की ताकत का फायदा उठाएगा। अपवाद
एडम बी।

1
या मान्य कोड की सूची को डेटाबेस या फ़ाइल से लोड किया जा सकता है। फिर एक हवाई अड्डा कोड को उस सूची में शामिल होने के लिए जांचा जाता है। यह वह है जो आप आमतौर पर तब करते हैं जब आप अब मानों को हार्डकोड नहीं करना चाहते हैं और / या सूची को प्रबंधित करने के लिए लंबी हो जाती है।
नील

@BenCottrell जो कोड जीन और T4 टेम्पलेट के लिए ठीक है।
रबरडॉक

3

जिन कारणों से आप अधिक GC गतिविधि देख रहे हैं, उनमें से एक यह है कि आप अब एक दूसरा स्ट्रिंग बना रहे हैं - .ToUpperInvariant()मूल स्ट्रिंग का संस्करण। कंस्ट्रक्टर के चलने के बाद मूल स्ट्रिंग जीसी अधिकार के लिए योग्य है और Airportऑब्जेक्ट के रूप में एक ही समय में दूसरा पात्र है। आप इसे एक अलग तरीके से कम कर सकते हैं (तीसरे पैरामीटर पर ध्यान दें string.Equals()):

public sealed class Airport : IEquatable<Airport>
{
    public Airport(string code)
    {
        if (code == null)
        {
            throw new ArgumentNullException(nameof(code));
        }

        if (code.Length != 3 || !char.IsLetter(code[0])
                             || !char.IsLetter(code[1]) || !char.IsLetter(code[2]))
        {
            throw new ArgumentException(
                "Must be a 3 letter airport code.",
                nameof(code));
        }

        Code = code;
    }

    public string Code { get; }

    public override string ToString()
    {
        return Code; // TODO: Upper-case it here if you really need to for display.
    }

    public bool Equals(Airport other)
    {
        return string.Equals(Code, other?.Code, StringComparison.InvariantCultureIgnoreCase);
    }

    public override bool Equals(object obj)
    {
        return obj is Airport airport && Equals(airport);
    }

    public override int GetHashCode()
    {
        return Code.GetHashCode();
    }

    public static bool operator ==(Airport left, Airport right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(Airport left, Airport right)
    {
        return !Equals(left, right);
    }
}

क्या यह अलग-अलग (लेकिन अलग-अलग पूंजीकृत) हवाई अड्डों के लिए अलग-अलग हैश कोड नहीं देता है?
हीरो वंडर्स

हाँ, मैं कल्पना करूँगा। धत तेरे की।
जेसी सी। स्लीपर

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

1
के संबंध में GetHashCode, बस का उपयोग करना चाहिए StringComparer.OrdinalIgnoreCase.GetHashCode(Code)या समान
मैथ्यू
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.