كيف يمكن لـ c# اكتشاف رمز التجزئة لكائن ما؟

StackOverflow https://stackoverflow.com/questions/102690

  •  01-07-2019
  •  | 
  •  

سؤال

هذا السؤال يأتي من المناقشة حول الصفوف.

بدأت أفكر في رمز التجزئة الذي يجب أن يحتوي عليه الصف.ماذا لو قبلنا فئة KeyValuePair كصف؟إنه لا يتجاوز طريقة GetHashCode()، لذلك ربما لن يكون على علم برموز التجزئة الخاصة بـ "الأطفال"...لذلك، سوف يستدعي وقت التشغيل Object.GetHashCode()، وهو ليس على علم ببنية الكائن الحقيقية.

ثم يمكننا إنشاء مثيلين من نوع مرجعي ما، وهما في الواقع متساويان، بسبب التحميل الزائد لـ GetHashCode() و Equals().واستخدمهم كـ "أطفال" في صفوف "لغش" القاموس.

لكنها لا تعمل!يكتشف وقت التشغيل بطريقة ما بنية صفنا ويستدعي GetHashCode المثقل بفئتنا!

كيف يعمل؟ما هو التحليل الذي أجراه Object.GetHashCode()؟

هل يمكن أن يؤثر ذلك على الأداء في بعض السيناريوهات السيئة، عندما نستخدم بعض المفاتيح المعقدة؟(ربما السيناريو المستحيل..لكن مازال)

خذ هذا الكود كمثال:

namespace csharp_tricks
{
    class Program
    {
        class MyClass
        {
            int keyValue;
            int someInfo;

            public MyClass(int key, int info)
            {
                keyValue = key;
                someInfo = info;
            }

            public override bool Equals(object obj)
            {
                MyClass other = obj as MyClass;
                if (other == null) return false;

                return keyValue.Equals(other.keyValue);
            }

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

        static void Main(string[] args)
        {
            Dictionary<object, object> dict = new Dictionary<object, object>();

            dict.Add(new KeyValuePair<MyClass,object>(new MyClass(1, 1), 1), 1);

            //here we get the exception -- an item with the same key was already added
            //but how did it figure out the hash code?
            dict.Add(new KeyValuePair<MyClass,object>(new MyClass(1, 2), 1), 1); 

            return;
        }
    }
}

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

  • كن حذرًا مع مفاتيحك ورموز التجزئة الخاصة بها :-)
  • بالنسبة لمفاتيح القاموس المعقدة، يجب عليك تجاوز Equals() وGetHashCode() بشكل صحيح.
هل كانت مفيدة؟

المحلول 4

يبدو أن لدي فكرة الآن.

اعتقدت أن KeyValuePair هو نوع مرجعي، لكنه ليس كذلك، بل هو بنية.ولذلك فهو يستخدم طريقة ValueType.GetHashCode().MSDN لأنه يقول:"يتم استخدام حقل واحد أو أكثر من النوع المشتق لحساب القيمة المرجعة".

إذا كنت ستأخذ نوعًا مرجعيًا حقيقيًا باعتباره "مزود صف" فسوف تغش في القاموس (أو تغش نفسك...).

using System.Collections.Generic;

namespace csharp_tricks
{
    class Program
    {
        class MyClass
        {
            int keyValue;
            int someInfo;

            public MyClass(int key, int info)
            {
                keyValue = key;
                someInfo = info;
            }

            public override bool Equals(object obj)
            {
                MyClass other = obj as MyClass;
                if (other == null) return false;

                return keyValue.Equals(other.keyValue);
            }

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

        class Pair<T, R>
        {
            public T First { get; set; }
            public R Second { get; set; }
        }

        static void Main(string[] args)
        {
            var dict = new Dictionary<Pair<int, MyClass>, object>();

            dict.Add(new Pair<int, MyClass>() { First = 1, Second = new MyClass(1, 2) }, 1);

            //this is a pair of the same values as previous! but... no exception this time...
            dict.Add(new Pair<int, MyClass>() { First = 1, Second = new MyClass(1, 3) }, 1);

            return;
        }
    }
}

نصائح أخرى

لا تتجاوز GetHashcode() و Equals() في الفئات القابلة للتغيير، بل تجاوزها فقط في الفئات أو الهياكل غير القابلة للتغيير، وإلا إذا قمت بتعديل كائن مستخدم كمفتاح، فلن يعمل جدول التجزئة بشكل صحيح بعد الآن (لن تتمكن من ذلك) استرداد القيمة المرتبطة بالمفتاح بعد تعديل الكائن الرئيسي)

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

فيما يلي تطبيقات التجزئة والمساواة المناسبة للصف الرباعي (يحتوي على 4 مكونات صفية بالداخل).يضمن هذا الرمز الاستخدام السليم لهذه المجموعة المحددة في HashSets والقواميس.

المزيد عن هذا الموضوع (بما في ذلك كود المصدر) هنا.

ملحوظة استخدام غير محدد الكلمة الأساسية (لتجنب التجاوزات) ورمي NullReferenceException إذا كان obj فارغًا (كما هو مطلوب بواسطة الطريقة الأساسية)

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj))
        throw new NullReferenceException("obj is null");
    if (ReferenceEquals(this, obj)) return true;
    if (obj.GetType() != typeof (Quad<T1, T2, T3, T4>)) return false;
    return Equals((Quad<T1, T2, T3, T4>) obj);
}

public bool Equals(Quad<T1, T2, T3, T4> obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return Equals(obj.Item1, Item1)
        && Equals(obj.Item2, Item2)
            && Equals(obj.Item3, Item3)
                && Equals(obj.Item4, Item4);
}

public override int GetHashCode()
{
    unchecked
    {
        int result = Item1.GetHashCode();
        result = (result*397) ^ Item2.GetHashCode();
        result = (result*397) ^ Item3.GetHashCode();
        result = (result*397) ^ Item4.GetHashCode();
        return result;
    }
}
public static bool operator ==(Quad<T1, T2, T3, T4> left, Quad<T1, T2, T3, T4> right)
{
    return Equals(left, right);
}


public static bool operator !=(Quad<T1, T2, T3, T4> left, Quad<T1, T2, T3, T4> right)
{
    return !Equals(left, right);
}

ألق نظرة على هذا بريد بقلم براد أبرامز وأيضًا تعليق بريان جرونكيماير للحصول على مزيد من المعلومات حول كيفية عمل object.GetHashCode.ألقِ نظرة أيضًا على التعليق الأول على مدونة Ayande بريد.لا أعرف ما إذا كانت الإصدارات الحالية من إطار العمل لا تزال تتبع هذه القواعد أو إذا كانت قد غيرتها بالفعل كما أشار براد.

لم يعد لدي مرجع الكتاب بعد الآن، وسأضطر إلى العثور عليه للتأكيد فقط، لكنني اعتقدت أن التجزئة الأساسية الافتراضية قد قامت للتو بتجزئة جميع أعضاء الكائن الخاص بك معًا.لقد تمكن من الوصول إليهم بسبب الطريقة التي يعمل بها CLR، لذلك لم يكن شيئًا يمكنك كتابته كما فعلوا.

هذا تمامًا من ذكرى شيء قرأته لفترة وجيزة، لذا خذه كما شئت.

يحرر: كان الكتاب داخل سي # من MS Press.واحد مع شفرة المنشار على الغلاف.أمضى المؤلف وقتًا طويلاً في شرح كيفية تنفيذ الأشياء في CLR، وكيفية ترجمة اللغة إلى MSIL، وما إلى ذلك.إلخ.إذا تمكنت من العثور على الكتاب، فلن تكون قراءته سيئة.

يحرر: قم بتكوين الرابط بشرط أن يبدو كذلك

يستخدم Object.gethashCode () حقلًا داخليًا في فئة System.Object لإنشاء قيمة التجزئة.يتم تعيين كل كائن تم إنشاؤه مفتاح كائن فريد ، مخزّن كعدد صحيح ، عند إنشائه.تبدأ هذه المفاتيح من 1 والزيادة في كل مرة يتم إنشاء كائن جديد من أي نوع.

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

لذلك ربما لن يكون على علم برموز التجزئة الخاصة بـ "الأطفال".

يبدو أن المثال الخاص بك يثبت خلاف ذلك :-) رمز التجزئة للمفتاح MyClass والقيمة 1 هو نفسه بالنسبة لكليهما KeyValuePair'س .يجب أن يستخدم تطبيق KeyValuePair كلا من Key و Value لرمز التجزئة الخاص به

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

خذ بعين الاعتبار حالة أكثر تعقيدًا:

public class HappyClass
{

    enum TheUnit
    {
        Points,
        Picas,
        Inches
    }

    class MyDistanceClass
    {
        int distance;
        TheUnit units;

        public MyDistanceClass(int theDistance, TheUnit unit)
        {
            distance = theDistance;

            units = unit;
        }
        public static int ConvertDistance(int oldDistance, TheUnit oldUnit, TheUnit newUnit)
        {
            // insert real unit conversion code here :-)
            return oldDistance * 100;
        }

        /// <summary>
        /// Figure out if we are equal distance, converting into the same units of measurement if we have to
        /// </summary>
        /// <param name="obj">the other guy</param>
        /// <returns>true if we are the same distance</returns>
        public override bool Equals(object obj)
        {
            MyDistanceClass other = obj as MyDistanceClass;
            if (other == null) return false;

            if (other.units != this.units)
            {
                int newDistance = MyDistanceClass.ConvertDistance(other.distance, other.units, this.units);
                return distance.Equals(newDistance);
            }
            else
            {
                return distance.Equals(other.distance);
            }


        }

        public override int GetHashCode()
        {
            // even if the distance is equal in spite of the different units, the objects are not
            return distance.GetHashCode() * units.GetHashCode();
        }
    }
    static void Main(string[] args)
    {

        // these are the same distance... 72 points = 1 inch
        MyDistanceClass distPoint = new MyDistanceClass(72, TheUnit.Points);
        MyDistanceClass distInch = new MyDistanceClass(1, TheUnit.Inch);

        Debug.Assert(distPoint.Equals(distInch), "these should be true!");
        Debug.Assert(distPoint.GetHashCode() != distInch.GetHashCode(), "But yet they are fundimentally different values");

        Dictionary<object, object> dict = new Dictionary<object, object>();

        dict.Add(new KeyValuePair<MyDistanceClass, object>(distPoint, 1), 1);

        //this should not barf
        dict.Add(new KeyValuePair<MyDistanceClass, object>(distInch, 1), 1);

        return;
    }

}

أساسًا...في حالة المثال الخاص بي، قد ترغب في إرجاع كائنين لهما نفس المسافة "صحيح" لـ Equals، ولكن مع إرجاع رموز تجزئة مختلفة.

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