c# - सी#में एक शब्दकोश कुंजी के रूप में टुपल बनाम स्ट्रिंग




.net caching (6)

मेरे पास एक कैश है जिसे मैं एक ConcurrentDictionary का उपयोग करके कार्यान्वित करता हूं, मुझे जो डेटा रखना है उसे 5 पैरामीटर पर निर्भर करता है। तो इसे कैश से प्राप्त करने का तरीका यह है: (मैं सादगी के लिए यहां केवल 3 पैरामीटर दिखाता हूं, और मैंने स्पष्टता के लिए कारडाटा का प्रतिनिधित्व करने के लिए डेटा प्रकार बदल दिया)

public CarData GetCarData(string carModel, string engineType, int year);

मुझे आश्चर्य है कि मेरे ConcurrentDictionary में किस प्रकार की कुंजी का उपयोग करना बेहतर होगा, मैं इसे इस तरह कर सकता हूं:

var carCache = new ConcurrentDictionary<string, CarData>();
// check for car key
bool exists = carCache.ContainsKey(string.Format("{0}_{1}_{2}", carModel, engineType, year);

या इस तरह:

var carCache = new ConcurrentDictionary<Tuple<string, string, int>, CarData>();
// check for car key
bool exists = carCache.ContainsKey(new Tuple(carModel, engineType, year));

मैं इन पैरामीटरों को किसी अन्य स्थान पर एक साथ नहीं उपयोग करता हूं, इसलिए उन्हें एक साथ रखने के लिए कक्षा बनाने के लिए कोई औचित्य नहीं है।

मैं जानना चाहता हूं कि प्रदर्शन और रखरखाव के मामले में कौन सा दृष्टिकोण बेहतर है।


मैं जानना चाहता हूं कि प्रदर्शन और रखरखाव के मामले में कौन सा दृष्टिकोण बेहतर है।

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

रखरखाव के बारे में, समाधान जो स्वत: दस्तावेजों को बेहतर बनाता है और बेहतर स्केलेबिलिटी विजेता होना चाहिए। इस मामले में, कोड इतना छोटा है कि ऑटोडोक्यूशनेशन उस मुद्दे का अधिक नहीं है। स्केलेबिलिटी पॉइंट ऑफ व्यू से, Tuple<T1, T2, ...> , सबसे अच्छा समाधान Tuple<T1, T2, ...> :

  • आपको मुफ्त समानता अर्थशास्त्र मिलता है जिसे आपको बनाए रखने की आवश्यकता नहीं है।
  • टकराव संभव नहीं है, कुछ ऐसा जो सत्य नहीं है यदि आप स्ट्रिंग कॉन्सटेनेशन समाधान चुनते हैं:

    var param1 = "Hey_I'm a weird string";
    var param2 = "!"
    var param3 = 1;
    key = "Hey_I'm a weird string_!_1";
    
    var param1 = "Hey";
    var param2 = "I'm a weird string_!"
    var param3 = 1;
    key = "Hey_I'm a weird string_!_1";
    

    हाँ, अब तक पहुंचा, लेकिन, सिद्धांत रूप में, पूरी तरह से संभव है और आपका प्रश्न भविष्य में अज्ञात घटनाओं के बारे में सटीक है, इसलिए ...

  • और आखिरी, लेकिन कम से कम नहीं, संकलक आपको कोड को बनाए रखने में मदद करता है । यदि, उदाहरण के लिए, कल आपको अपनी कुंजी पर param4 जोड़ना param4 , Tuple<T1, T2, T3, T4> दृढ़ता से आपकी कुंजी टाइप करेगा। दूसरी तरफ आपकी स्ट्रिंग param4 बिना आनंदपूर्वक खुश जनरेटिंग कुंजियों पर रह param4 और आप नहीं जानते कि क्या हो रहा है जब तक कि आपका क्लाइंट आपको कॉल न करे क्योंकि उनका सॉफ़्टवेयर अपेक्षित काम नहीं कर रहा है।


आईएमएचओ, मैं ऐसे मामलों में कुछ मध्यवर्ती संरचना का उपयोग करना पसंद करता हूं (आपके मामले में यह Tuple होगा)। इस तरह के दृष्टिकोण पैरामीटर और अंत-लक्ष्य शब्दकोश के बीच अतिरिक्त परत बनाता है। बेशक, यह उद्देश्यों पर निर्भर करेगा। उदाहरण के लिए इस तरह से आप पैरामीटर के मामूली संक्रमण नहीं बना सकते हैं (उदाहरण के लिए कंटेनर डेटा "विकृत" हो सकता है)।


एक जटिल कुंजी के रूप में उपयोग किए जाने वाले इन पैरामीटर को अच्छे स्वाद के लिए कक्षा में संयुक्त करने के लिए अच्छा औचित्य है। टुपल <...> क्लास के रूप में एक शब्दकोश कुंजी उद्देश्यों के लिए उपयुक्त नहीं है क्योंकि GetHashCode कार्यान्वयन हमेशा 0 देता है। इसलिए आपको नई कक्षा बनाना होगा जो या तो टुपल या ब्रांड नए कंटेनर से लिया गया है। प्रदर्शन परिप्रेक्ष्य से सब कुछ GetHashCode और समान विधियों के कार्यान्वयन की दक्षता पर निर्भर करता है।


कस्टम कुंजी क्लास को कार्यान्वित करें और सुनिश्चित करें कि यह ऐसे उपयोग मामलों के लिए उपयुक्त है, यानी IEquatable लागू IEquatable और कक्षा को अपरिवर्तनीय बनाएं :

public class CacheKey : IEquatable<CacheKey>
{
    public CacheKey(string param1, string param2, int param3)
    {
        Param1 = param1;
        Param2 = param2;
        Param3 = param3;
    }

    public string Param1 { get; }

    public string Param2 { get; }

    public int Param3 { get; }

    public bool Equals(CacheKey other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return string.Equals(Param1, other.Param1) && string.Equals(Param2, other.Param2) && Param3 == other.Param3;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != GetType()) return false;
        return Equals((CacheKey)obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = Param1?.GetHashCode() ?? 0;
            hashCode = (hashCode * 397) ^ (Param2?.GetHashCode() ?? 0);
            hashCode = (hashCode * 397) ^ Param3;
            return hashCode;
        }
    }
}

यह GetHashCode() कार्यान्वयन है कि Resharper इसे कैसे उत्पन्न करता है। यह एक अच्छा सामान्य उद्देश्य कार्यान्वयन है। आवश्यकतानुसार अनुकूलित करें।

वैकल्पिक रूप से, Equ (मैं उस लाइब्रेरी का निर्माता हूं) जैसे कुछ का उपयोग करता हूं जो स्वचालित रूप से Equals और GetHashCode कार्यान्वयन उत्पन्न करता है। यह सुनिश्चित करेगा कि इन विधियों में हमेशा CacheKey कक्षा के सभी सदस्यों को शामिल किया CacheKey , इसलिए कोड बनाए रखने के लिए बहुत आसान हो जाता है । ऐसा कार्यान्वयन तब इस तरह दिखता है:

public class CacheKey : MemberwiseEquatable<CacheKey>
{
    public CacheKey(string param1, string param2, int param3)
    {
        Param1 = param1;
        Param2 = param2;
        Param3 = param3;
    }

    public string Param1 { get; }

    public string Param2 { get; }

    public int Param3 { get; }
}

नोट: आपको स्पष्ट रूप से सार्थक संपत्ति नामों का उपयोग करना चाहिए, अन्यथा एक कस्टम क्लास पेश करना Tuple का उपयोग करने पर अधिक लाभ नहीं प्रदान करता है।


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

TestClass
initialization: 00:00:11.8787245
Retrieving: 00:00:06.3609475

TestTuple
initialization: 00:00:14.6531189
Retrieving: 00:00:08.5906265

TestValueTuple
initialization: 00:00:10.8491263
Retrieving: 00:00:06.6928401

TestFlat
initialization: 00:00:16.6559780
Retrieving: 00:00:08.5257845

परीक्षण के लिए कोड नीचे है:

static void TestValueTuple(int n = 10000000)
{
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    var tupleDictionary = new Dictionary<(string, string, int), string>();

    for (var i = 0; i < n; i++)
    {
        tupleDictionary.Add((i.ToString(), i.ToString(), i), i.ToString());
    }
    stopwatch.Stop();
    Console.WriteLine($"initialization: {stopwatch.Elapsed}");

    stopwatch.Restart();

    for (var i = 0; i < n; i++)
    {
        var s = tupleDictionary[(i.ToString(), i.ToString(), i)];
    }

    stopwatch.Stop();
    Console.WriteLine($"Retrieving: {stopwatch.Elapsed}");
}

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

इसके बजाए, आपको ValueTuple से एक struct , या तो एक कस्टम, या ValueTuple उपयोग करना चाहिए। ValueTuple पैकेज :

var myCache = new ConcurrentDictionary<ValueTuple<string, string, int>, CachedData>();
bool exists = myCache.ContainsKey(ValueTuple.Create(param1, param2, param3));

सी # 7.0 इस कोड को लिखने में आसान बनाने के लिए सिंटैक्स चीनी भी है (लेकिन आपको चीनी के बिना ValueTuple का उपयोग शुरू करने के लिए सी # 7.0 की प्रतीक्षा करने की आवश्यकता नहीं है):

var myCache = new ConcurrentDictionary<(string, string, int), CachedData>();
bool exists = myCache.ContainsKey((param1, param2, param3));




concurrentdictionary