.NET अद्वितीय ऑब्जेक्ट पहचानकर्ता


118

क्या किसी उदाहरण की विशिष्ट पहचानकर्ता होने का एक तरीका है?

GetHashCode()एक ही उदाहरण की ओर इशारा करते हुए दो संदर्भों के लिए समान है। हालांकि, दो अलग-अलग उदाहरण (काफी आसानी से) एक ही हैश कोड प्राप्त कर सकते हैं:

Hashtable hashCodesSeen = new Hashtable();
LinkedList<object> l = new LinkedList<object>();
int n = 0;
while (true)
{
    object o = new object();
    // Remember objects so that they don't get collected.
    // This does not make any difference though :(
    l.AddFirst(o);
    int hashCode = o.GetHashCode();
    n++;
    if (hashCodesSeen.ContainsKey(hashCode))
    {
        // Same hashCode seen twice for DIFFERENT objects (n is as low as 5322).
        Console.WriteLine("Hashcode seen twice: " + n + " (" + hashCode + ")");
        break;
    }
    hashCodesSeen.Add(hashCode, null);
}

मैं डिबगिंग एडिन लिख रहा हूं, और मुझे एक संदर्भ के लिए किसी तरह की आईडी प्राप्त करने की आवश्यकता है जो कार्यक्रम के चलने के दौरान अद्वितीय है।

मैं पहले से ही उदाहरण के आंतरिक एडीआरडीएस प्राप्त करने में कामयाब रहा, जो तब तक अद्वितीय है जब तक कचरा कलेक्टर (जीसी) ढेर को संकुचित नहीं करता (= वस्तुओं को स्थानांतरित करता है = पते बदलता है)।

स्टैक ओवरफ्लो प्रश्न Object.GetHashCode () के लिए डिफ़ॉल्ट कार्यान्वयन संबंधित हो सकता है।

ऑब्जेक्ट मेरे नियंत्रण में नहीं हैं क्योंकि मैं डिबगर API का उपयोग करके डिबग किए जा रहे प्रोग्राम में ऑब्जेक्ट्स एक्सेस कर रहा हूं। यदि मैं वस्तुओं के नियंत्रण में था, तो अपने स्वयं के विशिष्ट पहचानकर्ताओं को जोड़ना तुच्छ होगा।

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

Build a hashtable: 'hashCode' -> (list of objects with hash code == 'hashCode')
Find if object seen(o) {
    candidates = hashtable[o.GetHashCode()] // Objects with the same hashCode.
    If no candidates, the object is new
    If some candidates, compare their addresses to o.Address
        If no address is equal (the hash code was just a coincidence) -> o is new
        If some address equal, o already seen
}

जवाबों:


42

संदर्भ है वस्तु के लिए अद्वितीय पहचानकर्ता। मैं इसे स्ट्रिंग जैसे किसी भी चीज़ में परिवर्तित करने के किसी भी तरीके के बारे में नहीं जानता हूँ। संदर्भ का मूल्य संघनन के दौरान बदल जाएगा (जैसा कि आपने देखा है), लेकिन हर पिछले मूल्य A को मान B में बदल दिया जाएगा, जहाँ तक जैसा कि सुरक्षित कोड का संबंध है, यह अभी भी एक अद्वितीय आईडी है।

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


1
मुझे लगता है कि आपके द्वारा ट्रैक किए गए सभी संदर्भों में आपके द्वारा किए गए लुकअप के लिए अनुमान है: समान ऑब्जेक्ट के लिए कमजोर होना एक दूसरे के बराबर नहीं है, इसलिए आप वास्तव में बहुत कुछ नहीं कर सकते।
रोमन स्टार्कोव 23:10

1
प्रत्येक वस्तु को एक विशिष्ट 64-बिट आईडी निर्दिष्ट करने के लिए कुछ उपयोगिता हो सकती है, खासकर अगर ऐसी आईडी क्रमिक रूप से जारी की गई हो। मुझे यकीन नहीं है कि उपयोगिता लागत को उचित ठहराएगी, लेकिन अगर कोई दो अलग-अलग अपरिवर्तनीय वस्तुओं की तुलना करता है और उन्हें समान पाता है, तो ऐसी चीज उपयोगी हो सकती है; यदि एक संभव हो तो पुराने के संदर्भ में नए के संदर्भ को अधिलेखित कर देता है, एक समान लेकिन अलग-अलग वस्तुओं के लिए कई निरर्थक संदर्भ होने से बच सकता है।
सुपरकैट

1
"पहचानकर्ता।" मुझे नहीं लगता कि इस शब्द का मतलब है कि आप क्या सोचते हैं इसका मतलब है।
स्लिप डी। थॉम्पसन

5
@ SlippD.Thompson: नहीं, यह अभी भी 1-से-1 संबंध है। केवल एक ही संदर्भ मूल्य है जो किसी भी दी गई वस्तु को संदर्भित करता है। वह मान कई बार मेमोरी में दिखाई दे सकता है (जैसे कि कई वेरिएबल्स की वैल्यू के रूप में), लेकिन यह अभी भी सिंगल वैल्यू है। यह एक घर के पते की तरह है: मैं अपने घर के पते को कागज के कई टुकड़ों पर नीचे लिख सकता हूं, लेकिन यह अभी भी मेरे घर के लिए पहचानकर्ता है। किसी भी दो गैर-समान संदर्भ मानों को अलग-अलग वस्तुओं को संदर्भित करना चाहिए - कम से कम C # में।
जॉन स्कीट

1
@ सुपरकैट: मुझे लगता है कि हम अपनी पहचान "अलग-थलग रहने वाले" की समझ में भिन्न हो सकते हैं - लेकिन मुझे लगता है कि हम शायद किसी को भी आगे जाने के लिए किसी की मदद नहीं कर रहे हैं। हमारे पास लंबाई पर चर्चा करने वाले विषयों में से एक है। हम कभी भी व्यक्ति में मिलते हैं ...
जॉन स्कीट

72

.NET 4 और बाद में केवल

सबके लिए अच्छी खबर है!

इस काम के लिए सही उपकरण .NET 4 में बनाया गया है और इसे कहा जाता है ConditionalWeakTable<TKey, TValue>। यह क्लास:

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

5
बस पूर्णता के लिए: अपने आंतरिक कामकाज ConditionalWeakTableपर निर्भर करता है RuntimeHelpers.GetHashCodeऔर object.ReferenceEqualsकरने के लिए। व्यवहार एक ही है IEqualityComparer<T>जो इन दो तरीकों का उपयोग करता है। यदि आपको प्रदर्शन की आवश्यकता है, तो मैं वास्तव में ऐसा करने का सुझाव देता हूं, क्योंकि ConditionalWeakTableइसके धागे को सुरक्षित बनाने के लिए इसके सभी कार्यों के चारों ओर एक ताला है।
atlaste

1
@StefandeBruijn: एक ConditionalWeakTableप्रत्येक के लिए एक संदर्भ रखता है Valueजो केवल उतना ही मजबूत है जितना कि संदर्भ कहीं और से संबंधित है Key। एक वस्तु जिसका ConditionalWeakTableब्रह्मांड में कहीं भी एकमात्र विलुप्त होने वाला संदर्भ है, कुंजी के होने पर स्वतः ही अस्तित्व में नहीं आएगा।
सुपरकैट

41

ObjectIDGenerator वर्ग की जाँच की ? यह वही है जो आप करने का प्रयास कर रहे हैं, और मार्क ग्रेवेल ने क्या वर्णन किया है।

ObjectIDGenerator पहले से पहचानी गई वस्तुओं पर नज़र रखता है। जब आप किसी ऑब्जेक्ट की ID मांगते हैं, तो ObjectIDGenerator को पता होता है कि क्या मौजूदा ID को वापस करना है, या एक नई ID को बनाना और याद रखना है।

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

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

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


5
रिफ्लेक्टर मुझे बताता है कि ObjectIDGenerator डिफ़ॉल्ट GetHashCode कार्यान्वयन (यानी यह उपयोगकर्ता अधिभार का उपयोग नहीं करता है) पर निर्भर एक हैशटेबल है।
एंटोन टायखी

जब मुद्रण योग्य अद्वितीय आईडी की आवश्यकता होती है तो संभवतः सबसे अच्छा समाधान।
रोमन स्टार्कोव

ObjectIDGenerator फोन पर भी लागू नहीं होता है।
एंथनी वेसर

मुझे ठीक से समझ नहीं आ रहा है कि ObjectIDGenerator क्या कर रहा है, लेकिन यह काम करने लगता है, तब भी जब यह RuntimeHelpers.GetHashCode का उपयोग कर रहा है। मैंने केवल और केवल RuntimeHelpers.GetHashCode का परीक्षण किया और अपने मामले में विफल रहा।
डैनियल बायसर

+1 - बहुत चालाक काम करता है (डेस्कटॉप पर, कम से कम)।
हॉट लिक्स

37

RuntimeHelpers.GetHashCode()मदद कर सकते हैं ( MSDN )।


2
यह अच्छी तरह से मदद कर सकता है, लेकिन आधार ऑब्जेक्ट का उपयोग करके IIRC.GetHashCode () को एक सिंक ब्लॉक आवंटित करने की आवश्यकता है, जो मुफ़्त नहीं है। हालांकि अच्छा विचार है - मेरी ओर से +1।
जॉन स्कीट

धन्यवाद, मैं इस विधि को नहीं जानता था। हालांकि, यह या तो अद्वितीय हैश कोड का उत्पादन नहीं करता है (प्रश्न में नमूना कोड के समान ही व्यवहार करता है)। हालांकि उपयोगी होगा यदि उपयोगकर्ता डिफ़ॉल्ट संस्करण को कॉल करने के लिए हैश कोड को ओवरराइड करता है।
मार्टिन कोनिसक

1
यदि आप उनमें से बहुत अधिक (नीचे देखें) की आवश्यकता नहीं है, तो आप GCHandle का उपयोग कर सकते हैं।
एंटोन टायखी

42
एक उच्च सम्मानित लेखक द्वारा .NET पर एक पुस्तक में कहा गया है कि RuntimeHelpers.GetHashCode () एक कोड उत्पन्न करेगा जो एक AppDomain के भीतर अद्वितीय है, और Microsoft ने विधि GetUniqueObidID का नाम दिया हो सकता है। यह केवल गलत है। परीक्षण में, मैंने पाया कि मैं आमतौर पर उस समय तक एक डुप्लिकेट प्राप्त कर लेता हूं जब मैंने एक वस्तु (10,000 एक WinForms TextBox) का निर्माण किया था, और पिछले 30,000 कभी नहीं प्राप्त कर सका। माना जाता है कि अद्वितीय वस्तुओं पर 1/10 से अधिक नहीं बनाने के बाद उत्पादन की प्रणाली में रुक-रुक कर होने वाली विशिष्टता पर कोड क्रैश का कारण बन रहा था।
जन हेटिच जनवरी २11

3
@supercat: अहा - को 2003 से कुछ सबूत मिले हैं, जो .NET 1.0 और 1.1 से था। ऐसा लगता है कि वे .NET 2 के लिए बदलने की योजना बना रहे थे: blogs.msdn.com/b/brada/archive/2003/09/30/50396.aspx
जॉन स्कीट

7

आप एक सेकंड में अपनी बात विकसित कर सकते हैं। उदाहरण के लिए:

   class Program
    {
        static void Main(string[] args)
        {
            var a = new object();
            var b = new object();
            Console.WriteLine("", a.GetId(), b.GetId());
        }
    }

    public static class MyExtensions
    {
        //this dictionary should use weak key references
        static Dictionary<object, int> d = new Dictionary<object,int>();
        static int gid = 0;

        public static int GetId(this object o)
        {
            if (d.ContainsKey(o)) return d[o];
            return d[o] = gid++;
        }
    }   

आप चुन सकते हैं कि आप अपने दम पर अद्वितीय आईडी के रूप में क्या पसंद करेंगे, उदाहरण के लिए, System.Guid.NewGuid () या सबसे तेज़ पहुंच के लिए बस पूर्णांक।


2
यदि आपको इसकी आवश्यकता है तो Disposeबग्स में मदद नहीं करेगा , क्योंकि यह किसी भी तरह के निपटान को रोक देगा।
रोमन स्टार्कोव

1
यह काफी काम नहीं करता है क्योंकि शब्दकोश पहचान के बजाय समानता का उपयोग करता है, ऑब्जेक्ट के लिए समान मूल्यों को लौटाने वाली वस्तुओं को ढहता है। ईक्ल्स
एंथनी

1
हालांकि यह वस्तु को जीवित रखेगा।
मार्टिन लॉटरिंग

1
@MartinLottering क्या होगा अगर वह ConditionalWeakTable <ऑब्जेक्ट, idType> का उपयोग करता है?
डेमेट्रिस लेप्टोस

7

इस विधि के बारे में कैसे:

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

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

बाहर निकलने पर मूल मूल्य पर वापस पहली वस्तु में फ़ील्ड सेट करना न भूलें।

समस्या?


4

विज़ुअल स्टूडियो में एक अद्वितीय ऑब्जेक्ट पहचानकर्ता बनाना संभव है: घड़ी की खिड़की में, ऑब्जेक्ट चर पर राइट-क्लिक करें और संदर्भ मेनू से मेक ऑब्जेक्ट आईडी चुनें।

दुर्भाग्य से, यह एक मैनुअल कदम है, और मुझे नहीं लगता कि पहचानकर्ता को कोड के माध्यम से पहुँचा जा सकता है।


विजुअल स्टूडियो के किन संस्करणों में यह सुविधा है? उदाहरण के लिए, एक्सप्रेस संस्करण?
पीटर मोर्टेंसन

3

आपको ऐसे पहचानकर्ता को स्वयं, मैन्युअल रूप से - उदाहरण के अंदर, या बाह्य रूप से असाइन करना होगा।

डेटाबेस से संबंधित रिकॉर्ड के लिए, प्राथमिक कुंजी उपयोगी हो सकती है (लेकिन आप अभी भी डुप्लिकेट प्राप्त कर सकते हैं)। वैकल्पिक रूप से, या तो एक का उपयोग करें Guid, या अपने स्वयं के काउंटर को रखें, का उपयोग करके आवंटित करें Interlocked.Increment(और इसे काफी बड़ा करें कि यह अतिप्रवाह की संभावना नहीं है)।


2

मुझे पता है कि इसका उत्तर दिया जा चुका है, लेकिन यह ध्यान रखना कम से कम उपयोगी है कि आप इसका उपयोग कर सकते हैं:

http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx

जो आपको सीधे तौर पर "यूनिक आईडी" नहीं देगा, लेकिन WeakReferences (और हैशसेट?) के साथ मिलकर आपको विभिन्न उदाहरणों को ट्रैक करने का एक बहुत आसान तरीका दे सकता है।


1

जो जानकारी मैं यहां देता हूं वह नई नहीं है, मैंने इसे पूर्णता के लिए जोड़ा है।

इस कोड का विचार काफी सरल है:

  • वस्तुओं को एक विशिष्ट आईडी की आवश्यकता होती है, जो डिफ़ॉल्ट रूप से नहीं होती है। इसके बजाय, हमें अगली सबसे अच्छी चीज़ पर भरोसा करना होगा, जो RuntimeHelpers.GetHashCodeहमें एक विशिष्ट आईडी की तरह प्राप्त करना है
  • विशिष्टता की जांच करने के लिए, इसका मतलब है कि हमें उपयोग करने की आवश्यकता है object.ReferenceEquals
  • हालाँकि, हम अभी भी एक यूनिक आईडी रखना चाहेंगे, इसलिए मैंने एक जोड़ा GUID, जो कि यूनिक है।
  • क्योंकि मुझे सब कुछ लॉक करना पसंद नहीं है अगर मेरे पास नहीं है, तो मैं उपयोग नहीं करता हूं ConditionalWeakTable

संयुक्त, जो आपको निम्नलिखित कोड देगा:

public class UniqueIdMapper
{
    private class ObjectEqualityComparer : IEqualityComparer<object>
    {
        public bool Equals(object x, object y)
        {
            return object.ReferenceEquals(x, y);
        }

        public int GetHashCode(object obj)
        {
            return RuntimeHelpers.GetHashCode(obj);
        }
    }

    private Dictionary<object, Guid> dict = new Dictionary<object, Guid>(new ObjectEqualityComparer());
    public Guid GetUniqueId(object o)
    {
        Guid id;
        if (!dict.TryGetValue(o, out id))
        {
            id = Guid.NewGuid();
            dict.Add(o, id);
        }
        return id;
    }
}

इसका उपयोग करने के लिए, इसका एक उदाहरण बनाएं UniqueIdMapperऔर GUID का उपयोग वस्तुओं के लिए करें।


परिशिष्ट

इसलिए, यहां कुछ और चल रहा है; मुझे थोड़ा नीचे लिखें ConditionalWeakTable

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

जिज्ञासु नहीं? आखिरकार, जब कोई वस्तु जीसी द्वारा एकत्र की जा रही है, तो यह जांचती है कि क्या वस्तु के संदर्भ हैं, और यदि हैं, तो यह उन्हें एकत्र करता है। तो अगर वहाँ से एक वस्तु है ConditionalWeakTable, तो संदर्भित वस्तु को क्यों एकत्र किया जाएगा?

ConditionalWeakTableएक छोटी सी चाल का उपयोग करता है, जो कुछ अन्य .NET संरचनाओं का भी उपयोग करता है: ऑब्जेक्ट के संदर्भ को संग्रहीत करने के बजाय, यह वास्तव में एक इंटपार्ट को संग्रहीत करता है। क्योंकि यह एक वास्तविक संदर्भ नहीं है, इसलिए वस्तु को एकत्र किया जा सकता है।

तो, इस बिंदु पर पता करने के लिए 2 समस्याएं हैं। सबसे पहले, वस्तुओं को ढेर पर स्थानांतरित किया जा सकता है, इसलिए हम IntPtr के रूप में क्या उपयोग करेंगे? और दूसरा, हम कैसे जानते हैं कि वस्तुओं का एक सक्रिय संदर्भ है?

  • ऑब्जेक्ट को ढेर पर पिन किया जा सकता है, और इसके असली पॉइंटर को स्टोर किया जा सकता है। जब जीसी हटाने के लिए ऑब्जेक्ट को हिट करता है, तो यह इसे अनपिन करता है और इसे इकट्ठा करता है। हालांकि, इसका मतलब यह होगा कि हमें एक पिन किया हुआ संसाधन मिलेगा, जो एक अच्छा विचार नहीं है यदि आपके पास बहुत सारी वस्तुएं हैं (स्मृति विखंडन के मुद्दों के कारण)। यह शायद यह नहीं है कि यह कैसे काम करता है।
  • जब GC एक ऑब्जेक्ट को स्थानांतरित करता है, तो वह वापस कॉल करता है, जो तब संदर्भों को अपडेट कर सकता है। यह हो सकता है कि बाहरी कॉल द्वारा इसे कैसे लागू किया जाए DependentHandle- लेकिन मेरा मानना ​​है कि यह थोड़ा अधिक परिष्कृत है।
  • ऑब्जेक्ट के लिए सूचक ही नहीं, बल्कि GC से सभी ऑब्जेक्ट की सूची में एक पॉइंटर संग्रहीत किया जाता है। इस सूची में IntPtr या तो एक सूचकांक या एक सूचक है। सूची केवल तब बदलती है जब कोई वस्तु पीढ़ियों को बदलती है, जिस बिंदु पर एक साधारण कॉलबैक बिंदुओं को अपडेट कर सकता है। यदि आपको याद है कि मार्क और स्वीप कैसे काम करता है, तो यह अधिक समझ में आता है। कोई पिनिंग नहीं है, और हटाना पहले जैसा है। मेरा मानना ​​है कि यह कैसे काम करता है DependentHandle

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

यदि हम मानते हैं कि वे इस समाधान का उपयोग करते हैं, तो हम दूसरी समस्या का समाधान भी कर सकते हैं। मार्क और स्वीप एल्गोरिथ्म का ट्रैक रखता है कि किन वस्तुओं को एकत्र किया गया है; जैसे ही इसे एकत्र किया गया है, हम इस बिंदु पर जानते हैं। एक बार ऑब्जेक्ट की जाँच करने के बाद यदि ऑब्जेक्ट वहाँ है, तो यह 'फ्री' कहता है, जो पॉइंटर और लिस्ट एंट्री को हटा देता है। वस्तु वास्तव में चली गई है।

इस बिंदु पर ध्यान देने वाली एक महत्वपूर्ण बात यह है कि यदि ConditionalWeakTableकई थ्रेड में अपडेट किया गया है और यदि यह थ्रेड सुरक्षित नहीं है तो चीजें बहुत गलत हो जाती हैं। परिणाम एक स्मृति रिसाव होगा। यही कारण है कि सभी कॉल ConditionalWeakTableएक सरल 'लॉक' करते हैं जो यह सुनिश्चित करता है कि ऐसा नहीं होता है।

एक और ध्यान देने वाली बात यह है कि प्रविष्टियों की सफाई एक बार में ही हो जाती है। जबकि वास्तविक वस्तुओं को जीसी द्वारा साफ किया जाएगा, प्रविष्टियां नहीं हैं। यही कारण है कि ConditionalWeakTableकेवल आकार में बढ़ता है। एक बार जब यह एक निश्चित सीमा से टकराता है (हैश में टकराव की संभावना से निर्धारित होता है), यह एक ट्रिगर करता है Resize, जो यह जांचता है कि क्या वस्तुओं को साफ करना है - यदि वे करते हैं, freeतो जीसी प्रक्रिया में, IntPtrहैंडल को हटा दिया जाता है।

मेरा मानना ​​है कि यह भी DependentHandleप्रत्यक्ष रूप से उजागर नहीं किया जाता है - आप चीजों के साथ खिलवाड़ नहीं करना चाहते हैं और इसके परिणामस्वरूप एक स्मृति रिसाव प्राप्त करते हैं। उसके लिए अगली सबसे अच्छी बात एक है WeakReference(जो IntPtrकिसी वस्तु के बजाय स्टोर भी करता है) - लेकिन दुर्भाग्य से इसमें 'निर्भरता' पहलू शामिल नहीं है।

आपके पास यांत्रिकी के चारों ओर खिलौना रखने के लिए क्या अवशेष है, ताकि आप कार्रवाई में निर्भरता देख सकें। इसे कई बार शुरू करना सुनिश्चित करें और परिणाम देखें:

class DependentObject
{
    public class MyKey : IDisposable
    {
        public MyKey(bool iskey)
        {
            this.iskey = iskey;
        }

        private bool disposed = false;
        private bool iskey;

        public void Dispose()
        {
            if (!disposed)
            {
                disposed = true;
                Console.WriteLine("Cleanup {0}", iskey);
            }
        }

        ~MyKey()
        {
            Dispose();
        }
    }

    static void Main(string[] args)
    {
        var dep = new MyKey(true); // also try passing this to cwt.Add

        ConditionalWeakTable<MyKey, MyKey> cwt = new ConditionalWeakTable<MyKey, MyKey>();
        cwt.Add(new MyKey(true), dep); // try doing this 5 times f.ex.

        GC.Collect(GC.MaxGeneration);
        GC.WaitForFullGCComplete();

        Console.WriteLine("Wait");
        Console.ReadLine(); // Put a breakpoint here and inspect cwt to see that the IntPtr is still there
    }

1
एक ConditionalWeakTableबेहतर हो सकता है, क्योंकि यह केवल वस्तुओं के लिए प्रतिनिधित्व को बनाए रखेगा जबकि संदर्भ उनके पास मौजूद थे। इसके अलावा, मेरा सुझाव है कि Int64यह GUID से बेहतर हो सकता है, क्योंकि यह वस्तुओं को एक स्थिर रैंक देने की अनुमति देगा । इस तरह की चीजें लॉकिंग परिदृश्यों में उपयोगी हो सकती हैं (जैसे कोई गतिरोध से बच सकता है यदि एक सभी कोड जिसे कई तालों को प्राप्त करने की आवश्यकता होगी, कुछ परिभाषित क्रम में ऐसा करता है, लेकिन इसके लिए काम करने के लिए एक निर्धारित क्रम होना चाहिए )।
सुपरकैट

@ supercat यकीन है कि longएस के बारे में ; यह आपके परिदृश्य पर निर्भर करता है - f.ex. में। वितरित सिस्टम यह कभी-कभी GUIDएस के साथ काम करने के लिए अधिक उपयोगी होता है । के रूप में ConditionalWeakTable: आप सही हैं; DependentHandleअलाउंस के लिए जांच (नोट: केवल जब चीज़ आकार बदलती है!), जो यहां उपयोगी हो सकती है। फिर भी, यदि आपको प्रदर्शन की आवश्यकता है, तो लॉकिंग एक मुद्दा बन सकता है, इसलिए उस स्थिति में इसका उपयोग करना दिलचस्प हो सकता है ... ईमानदार होने के लिए मैं व्यक्तिगत रूप से कार्यान्वयन को नापसंद करता हूं ConditionalWeakTable, जो संभवतः एक साधारण का उपयोग करने के मेरे पूर्वाग्रह की ओर जाता है Dictionary- यहां तक ​​कि हालाँकि आप सही हैं।
aste

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

@supercat मैं यह कैसे काम करता है पर एक परिशिष्ट पोस्ट करूँगा।
१०

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

0

यदि आप किसी विशिष्ट उपयोग के लिए अपने स्वयं के कोड में एक मॉड्यूल लिख रहे हैं, तो मेजकिनटोर की विधि MIGHT ने काम किया है। लेकिन कुछ समस्याएं हैं।

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

आपको यह नहीं मान लेना चाहिए कि समान हैश कोड ऑब्जेक्ट समानता का मतलब है।

दूसरा , मान लें कि आपके पास बहुत कम मात्रा में वस्तुएं हैं जो कि GetHashCode()ज्यादातर मामलों में काम करेंगी, इस विधि को कुछ प्रकारों से ओवरराइड किया जा सकता है।
उदाहरण के लिए, आप कुछ वर्ग C का उपयोग कर रहे हैं और यह GetHashCode()हमेशा 0. वापस करने के लिए ओवरराइड करता है। तब C के प्रत्येक ऑब्जेक्ट को समान हैश कोड मिलेगा। दुर्भाग्य से, Dictionary, HashTableऔर कुछ अन्य साहचर्य कंटेनरों इस विधि का उपयोग कर देगा:

एक हैश कोड एक संख्यात्मक मान है जो किसी वस्तु को हैश-आधारित संग्रह जैसे डिक्शनरी <TKey, TValue> क्लास, हैशटेबल क्लास, या DictionaryBase वर्ग से प्राप्त एक प्रकार की पहचान करने के लिए उपयोग किया जाता है। GetHashCode विधि एल्गोरिदम के लिए यह हैश कोड प्रदान करता है जिसे ऑब्जेक्ट समानता की त्वरित जांच की आवश्यकता होती है।

तो, इस दृष्टिकोण की महान सीमाएं हैं।

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

मैं सराहना करता हूं कि जॉन और साइमन ने अपने उत्तर पोस्ट किए हैं, और मैं एक कोड उदाहरण और नीचे दिए गए प्रदर्शन पर एक सुझाव पोस्ट करूंगा।

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Collections.Generic;


namespace ObjectSet
{
    public interface IObjectSet
    {
        /// <summary> check the existence of an object. </summary>
        /// <returns> true if object is exist, false otherwise. </returns>
        bool IsExist(object obj);

        /// <summary> if the object is not in the set, add it in. else do nothing. </summary>
        /// <returns> true if successfully added, false otherwise. </returns>
        bool Add(object obj);
    }

    public sealed class ObjectSetUsingConditionalWeakTable : IObjectSet
    {
        /// <summary> unit test on object set. </summary>
        internal static void Main() {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ObjectSetUsingConditionalWeakTable objSet = new ObjectSetUsingConditionalWeakTable();
            for (int i = 0; i < 10000000; ++i) {
                object obj = new object();
                if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }


        public bool IsExist(object obj) {
            return objectSet.TryGetValue(obj, out tryGetValue_out0);
        }

        public bool Add(object obj) {
            if (IsExist(obj)) {
                return false;
            } else {
                objectSet.Add(obj, null);
                return true;
            }
        }

        /// <summary> internal representation of the set. (only use the key) </summary>
        private ConditionalWeakTable<object, object> objectSet = new ConditionalWeakTable<object, object>();

        /// <summary> used to fill the out parameter of ConditionalWeakTable.TryGetValue(). </summary>
        private static object tryGetValue_out0 = null;
    }

    [Obsolete("It will crash if there are too many objects and ObjectSetUsingConditionalWeakTable get a better performance.")]
    public sealed class ObjectSetUsingObjectIDGenerator : IObjectSet
    {
        /// <summary> unit test on object set. </summary>
        internal static void Main() {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ObjectSetUsingObjectIDGenerator objSet = new ObjectSetUsingObjectIDGenerator();
            for (int i = 0; i < 10000000; ++i) {
                object obj = new object();
                if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }


        public bool IsExist(object obj) {
            bool firstTime;
            idGenerator.HasId(obj, out firstTime);
            return !firstTime;
        }

        public bool Add(object obj) {
            bool firstTime;
            idGenerator.GetId(obj, out firstTime);
            return firstTime;
        }


        /// <summary> internal representation of the set. </summary>
        private ObjectIDGenerator idGenerator = new ObjectIDGenerator();
    }
}

मेरे परीक्षण में, ObjectIDGeneratorवसीयत अपवाद को फेंक देगी कि forलूप में 10,000,000 ऑब्जेक्ट्स (10x से ऊपर कोड की तुलना में) बनाते समय बहुत अधिक ऑब्जेक्ट हैं ।

इसके अलावा, बेंचमार्क परिणाम यह है कि ConditionalWeakTableकार्यान्वयन की तुलना में ObjectIDGeneratorकार्यान्वयन 1.8x तेज है ।

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