سؤال

هل هناك طريقة للحصول على معرف فريد من نوعه للمثيل؟

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);
}

أنا أكتب Addin تصحيح الأخطاء، وأحتاج إلى الحصول على نوع من الهوية للحصول على مرجع فريد أثناء تشغيل البرنامج.

تمكنت بالفعل من الحصول على عنوان داخلي للمثيل، وهو أمر فريد من نوعه حتى تكوين جامع القمامة (GC) من كومة الكومة (= تحركات الكائنات = تغييرات العناوين).

سؤال مكدس تجاوز التنفيذ الافتراضي لكائن.gethashcode () قد تكون ذات صلة.

الكائنات ليست تحت سيطرتي كما يمكنني الوصول إلى الكائنات في برنامج يتم تصحيحه باستخدام API Debugger. إذا كنت في السيطرة على الكائنات، فإن إضافة معرفاتي الفريدة الخاصة بي ستكون تافهة.

أردت الهوية الفريدة لبناء معرف Hashtable -> كائن، لتكون قادرا على البحث عن كائنات مشوهة بالفعل. الآن أنا حلها مثل هذا:

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
}
هل كانت مفيدة؟

المحلول

المرجع يكون المعرف الفريد للكائن. لا أعرف أي طريقة لتحويل ذلك إلى أي شيء مثل سلسلة وما إلى ذلك. ستتغير قيمة المرجع أثناء الضغط (كما رأيت)، ولكن كل قيمة سابقة سيتم تغييرها إلى القيمة B، حتى الآن كما تشعر الكود الآمن بالقلق من أنه لا يزال معرف فريد.

إذا كانت الكائنات المعنية تحت سيطرتك، فيمكنك إنشاء رسم خرائط باستخدام المراجع الضعيفة (لتجنب منع جمع القمامة) من إشارة إلى معرف يختار اختيارك (GUID، عدد صحيح، أيا كان). من شأنها أن تضيف كمية معينة من النفقات العامة والتعقيد.

نصائح أخرى

.NET 4 وبعد ذلك فقط

اخبارسعيدة يا جماعة!

أداة مثالية لهذه المهمة مبنية في .NET 4 ويسمى ConditionalWeakTable<TKey, TValue>. وبعد هذه الفئة:

  • يمكن استخدامها لربط البيانات التعسفية مع مثيلات الكائنات المدارة مثل القاموس (على الرغم من ذلك يكون ليس القاموس)
  • لا يعتمد على عناوين الذاكرة، وكذلك محصن في GC المضغوط
  • لا يحتفظ بالكائنات على قيد الحياة فقط لأنها تم إدخالها كمفاتيح في الجدول، لذلك يمكن استخدامها دون جعل كل كائن في عمليةك تعيش إلى الأبد
  • يستخدم المساواة المرجعية لتحديد هوية الكائن؛ MoveOver، لا يمكن لمؤلفي الفئة تعديل هذا السلوك حتى يمكن استخدامه باستمرار على أشياء من أي نوع
  • يمكن ملؤها على الطاير، لذلك لا يتطلب منك حقن رمز الداخل كائن

سحب خارج Unessidgenerator. صف دراسي؟ هذا يفعل ما تحاول القيام به، وما يصفه Marc Gravell.

UnessIdedGenerator تتبع الكائنات المحددة مسبقا. عندما تسأل عن معرف الكائن، يعرف العيادة العددية ما إذا كنت تريد إرجاع المعرف الموجود، أو إنشاء معرف جديد وتذكره.

المعرفات فريدة من نوعها لحياة مثيل ObjectIDGenerator. بشكل عام، تستمر الحياة العيادة المتداولة طالما الشكل الذي أنشأه. تحتوي معرفات الكائنات على معنى فقط داخل مجرى متسلسل معين، وتستخدم لتتبع الكائنات التي تحتوي على مراجع إلى الآخرين داخل الرسم البياني للكائنات المتسلسل.

باستخدام جدول التجزئة، يحتفظ UnjessIDGenerator المعرف الذي تم تعيينه له الكائن. مراجع الكائن، والتي تحدد كل كائن بشكل فريد، هي عناوين في كومة الكومة جمع القمامة. يمكن أن تتغير القيم المرجعية للكائن أثناء التسلسل، ولكن يتم تحديث الجدول تلقائيا حتى تكون المعلومات صحيحة.

معرفات الكائن هي أرقام 64 بت. يبدأ التخصيص من واحد، لذلك لا يعد الصفر أبدا معرف كائن صالح. يمكن أن تختار صيغة قيمة صفرية لتمثيل مرجع كائن قيمته مرجعا فارغا (لا شيء في Visual Basic).

RuntimeHelpers.GetHashCode() قد يساعد (MSDN.).

يمكنك تطوير شيء خاص بك في الثانية. على سبيل المثال:

   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 () أو مجرد عدد صحيح من أجل الوصول الأسرع.

ماذا عن هذه الطريقة:

قم بتعيين حقل في الكائن الأول إلى قيمة جديدة. إذا كان الحقل نفسه في الكائن الثاني له نفس القيمة، فمن المحتمل أن تكون نفسها. خلاف ذلك، الخروج مختلفة.

الآن اضبط الحقل في الكائن الأول إلى قيمة جديدة مختلفة. إذا تغير نفس الحقل في الكائن الثاني إلى القيمة المختلفة، فهذا هو بالتأكيد نفس الحالة.

لا تنس تعيين الحقل في الكائن الأول مرة أخرى إلى القيمة الأصلية عند الخروج.

مشاكل؟

من الممكن إنشاء معرف كائن فريد في Visual Studio: في نافذة الساعة، انقر بزر الماوس الأيمن فوق متغير الكائن واختر جعل معرف الكائن من قائمة السياق.

لسوء الحظ، هذه خطوة يدوية، ولا أعتقد أن المعرف يمكن الوصول إليه عبر التعليمات البرمجية.

يجب عليك تعيين مثل هذا المعرف بنفسك، يدويا - إما داخل المثيل، أو خارجيا.

للسجلات المتعلقة بقاعدة بيانات، قد يكون المفتاح الأساسي مفيدا (ولكن لا يزال بإمكانك الحصول على التكرارات). بدلا من ذلك، إما استخدام أ Guid, ، أو الحفاظ على العداد الخاص بك، تخصيص استخدام Interlocked.Increment (وجعلها كبيرة بما يكفي أنه من غير المحتمل أن تفيض).

أعلم أن هذا قد تم الرد عليه، لكنه من المفيد على الأقل ملاحظة أنه يمكنك استخدام:

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

والتي لن تعطيك "معرف فريد" مباشرة، ولكن جنبا إلى جنب مع الضعف (وحمسة؟) يمكن أن تعطيك طريقة سهلة جدا لتتبع حالات مختلفة.

المعلومات التي أقدمها هنا ليست جديدة، لقد أضفت هذا للتو من أجل الاكتمال.

فكرة هذا الرمز بسيط للغاية:

  • تحتاج الكائنات إلى معرف فريد، وهو ليس هناك افتراضي. بدلا من ذلك، علينا الاعتماد على أفضل شيء التالي، وهو 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 يفعل بعض الأشياء. الشيء الأكثر أهمية هو أنه لا يهتم بجمع القمامة، وهذا هو: الكائنات التي تشير إليها في هذا الجدول سيتم جمعها بغض النظر. إذا كنت تبحث عن كائن، فإنه يعمل بشكل أساسي مثل القاموس أعلاه.

الغريب لا؟ بعد كل شيء، عندما يتم جمع كائن من قبل GC، فهو يتحقق إذا كانت هناك مراجع إلى الكائن، وإذا كان هناك، فإنه يجمعهم. لذلك إذا كان هناك كائن من ConditionalWeakTable, ، لماذا سيتم جمع الكائن المشار إليه بعد ذلك؟

ConditionalWeakTable يستخدم خدعة صغيرة، والتي تستخدمها بعض هياكل .NET الأخرى أيضا: بدلا من تخزين مرجع إلى الكائن، فإنه يخزن بالفعل INTPTR. لأن هذا ليس مرجع حقيقي، يمكن جمع الكائن.

لذلك، في هذه المرحلة، هناك مشكلتين لمعالجة. أولا، يمكن نقل الكائنات على كومة، فماذا سوف نستخدم ك Intptr؟ وثانيا، كيف نعرف أن الكائنات لديها مرجع نشط؟

  • يمكن تثبيت الكائن على كومة الكومة، ويمكن تخزين مؤشرها الحقيقي. عندما يضرب GC الكائن للإزالة، فإنه ينظر إليه ويجمعه. ومع ذلك، فإن ذلك يعني أننا نحصل على مورد مثبت، وهي ليست فكرة جيدة إذا كان لديك الكثير من الكائنات (بسبب مشاكل تجزئة الذاكرة). ربما هذا ليس كيف يعمل.
  • عندما يتحرك GC كائلا، فإنه يستدعي مرة أخرى، والذي يمكن بعد ذلك تحديث المراجع. قد يكون هذا هو كيفية تنفيذها من خلال المكالمات الخارجية DependentHandle - لكنني أعتقد أنه أكثر تطورا قليلا.
  • ليس المؤشر إلى الكائن نفسه، ولكن يتم تخزين مؤشر في قائمة جميع الكائنات من GC. INTPTR هو إما فهرس أو مؤشر في هذه القائمة. تتغير القائمة فقط عندما يغير كائن الأجيال للأجيال، عند نقطة اتصال بسيطة يمكنه تحديث المؤشرات. إذا كنت تتذكر كيفية عمل وأعمال الاجتياح، فهذا يجعل أكثر منطقية. لا يوجد تثبيت، وإزالة كما كان من قبل. أعتقد أن هذا هو كيف يعمل في DependentHandle.

يتطلب هذا الحل الأخير أن وقت التشغيل لا يعيد استخدام قوائم القائمة حتى يتم تحريرها بشكل صريح، كما يتطلب استرجاع جميع الكائنات من خلال مكالمة إلى وقت التشغيل.

إذا افترضنا أنهم يستخدمون هذا الحل، فيمكننا أيضا معالجة المشكلة الثانية. تبقي خوارزمية العلامة والإجراءات تتبع الكائنات التي تم جمعها؛ بمجرد جمعها، نحن نعرف في هذه المرحلة. بمجرد اختبارات الكائن إذا كان الكائن موجودا، فإنه يستدعي "مجاني"، مما يزيل المؤشر وإدخال القائمة. ذهب الكائن حقا.

شيء مهم يجب أن نلاحظ في هذه المرحلة هو أن الأمور تخطئ بشكل فظيع إذا ConditionalWeakTable يتم تحديثه في مؤشرات ترابط متعددة وإذا لم يكن مؤشر الترابط آمنا. ستكون النتيجة تسرب ذاكرة. هذا هو السبب في جميع المكالمات ConditionalWeakTable هل "قفل" بسيط يضمن أن هذا لا يحدث.

شيء آخر يجب ملاحظته هو أن إدخالات التنظيف يجب أن تحدث مرة واحدة في حين. في حين سيتم تنظيف الكائنات الفعلية من قبل GC، فإن الإدخالات ليست كذلك. هذا هو السبب ConditionalWeakTable ينمو فقط في الحجم. بمجرد أن تضرب حد معين (يحدده فرصة الاصطدام في التجزئة)، فإنه يؤدي إلى أ Resize, ، التي تتحقق إذا كان يجب تنظيف الكائنات - إذا فعلوا ذلك، free يسمى في عملية GC، وإزالة 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
    }

إذا كنت تكتب وحدة نمطية في التعليمات البرمجية الخاصة بك لاستخدام معين، طريقة ماجينيتور ربما عملت. ولكن هناك بعض المشاكل.

أولا, ، الوثيقة الرسمية لا ليس ضمان أن GetHashCode() إرجاع معرف فريد (انظر Object.Gethashcode طريقة ()):

يجب أن لا تفترض أن رموز التجزئة المتساوية تعني عن المساواة بين الكائنات.

ثانيا, ، افترض أن لديك كمية صغيرة جدا من الكائنات بحيث GetHashCode() ستعمل في معظم الحالات، يمكن تجاوز هذه الطريقة من قبل بعض الأنواع.
على سبيل المثال، أنت تستخدم بعض الفئة C وتتجاوز GetHashCode() لإرجاع دائما 0. ثم كل كائن C سيحصل على نفس رمز التجزئة. لسوء الحظ، Dictionary, HashTable وبعض الحاويات الإرشادية الأخرى ستجعل هذه الطريقة:

رمز التجزئة هو قيمة رقمية يتم استخدامها لإدراج وتحديد كائن في مجموعة تعتمد على التجزئة مثل القاموسu003CTKey, TValue> فئة، فئة Hashtable، أو نوع مشتق من فئة "DictleBase". توفر طريقة 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 سوف يرمي استثناءا للشكوى من أن هناك الكثير من الأشياء عند إنشاء 10،000،000 كائنات (10x من التعليمات البرمجية أعلاه) في for حلقه.

أيضا، والنتيجة القياسية هي أن ConditionalWeakTable التنفيذ هو 1.8x أسرع من ObjectIDGenerator تطبيق.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top