سؤال

كيف يمكن تطبيق الافتراضي GetHashCode() العمل ؟ وهل التعامل مع الهياكل ، الطبقات ، المصفوفات ، إلخ.بكفاءة وبشكل جيد بما فيه الكفاية ؟

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

هل كانت مفيدة؟

المحلول

namespace System {
    public class Object {
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal static extern int InternalGetHashCode(object obj);

        public virtual int GetHashCode() {
            return InternalGetHashCode(this);
        }
    }
}

internalgethishcode. يتم تعيين إلى endencenative :: gethashcode. وظيفة في CLR، والتي تبدو وكأنها هذه:

FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {  
    CONTRACTL  
    {  
        THROWS;  
        DISABLED(GC_NOTRIGGER);  
        INJECT_FAULT(FCThrow(kOutOfMemoryException););  
        MODE_COOPERATIVE;  
        SO_TOLERANT;  
    }  
    CONTRACTL_END;  

    VALIDATEOBJECTREF(obj);  

    DWORD idx = 0;  

    if (obj == 0)  
        return 0;  

    OBJECTREF objRef(obj);  

    HELPER_METHOD_FRAME_BEGIN_RET_1(objRef);        // Set up a frame  

    idx = GetHashCodeEx(OBJECTREFToObject(objRef));  

    HELPER_METHOD_FRAME_END();  

    return idx;  
}  
FCIMPLEND

التنفيذ الكامل لل gethashcodeex. كبيرة إلى حد ما، لذلك من الأسهل ربط فقط شفرة المصدر C ++.

نصائح أخرى

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

عند تجاوز المساواة، يجب أن يكون لديك دائما مطابقة Equals() و GetHashCode() (أي لقيمتين، إذا Equals() إرجاع صحيح هم يجب إرجاع نفس رمز التجزئة، ولكن التحدث هو ليس مطلوب) - ومن الشائع أيضا تقديم ==/!=المشغلين، وغالبا ما ينفذ IEquatable<T> جدا.

لتوليد رمز التجزئة، من الشائع استخدام مبلغ معين، لأن هذا يتجنب التصادمات على القيم المقترنة - على سبيل المثال، للحصول على تجزئة حقل أساسي 2

unchecked // disable overflow, for the unlikely possibility that you
{         // are compiling with overflow-checking enabled
    int hash = 27;
    hash = (13 * hash) + field1.GetHashCode();
    hash = (13 * hash) + field2.GetHashCode();
    return hash;
}

هذا لديه ميزة ما يلي:

  • تجزئة {1،2} ليست هي نفسها تجزئة {2،1}
  • التجزئة من {1،1} ليست هي نفسها تجزئة {2،2}

إلخ - والتي يمكن أن تكون شائعة إذا كانت فقط باستخدام مبلغ غير مرجح، أو XOR (^)، إلخ.

الوثائق ل GetHashCode طريقة ل موضوع يقول "يجب عدم استخدام التنفيذ الافتراضي لهذه الطريقة معرف كائن فريد لأغراض التجزئة." والآخر ل Valuetype. يقول "إذا قمت بتسمية طريقة Gethashode الخاصة بالنوع المشتقة، فمن غير المحتمل أن تكون قيمة الإرجاع مناسبة للاستخدام كإجراء أساسي في جدول التجزئة.".

أنواع البيانات الأساسية مثل byte, short, int, long, char و string تنفيذ طريقة Gethashcode جيدة. بعض الطبقات والهياكل الأخرى، مثل Point على سبيل المثال، تنفيذ GetHashCode الطريقة التي قد تكون أو لا تكون مناسبة لاحتياجاتك المحددة. عليك فقط أن تجربها لمعرفة ما إذا كانت جيدة بما فيه الكفاية.

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

منذ أن كنت لا يمكن العثور على إجابة هذا يفسر لماذا يجب علينا تجاوز GetHashCode و Equals مخصص البنيات ، لماذا تطبيق الافتراضي "ليس من المرجح أن تكون مناسبة لاستخدامها بوصفها الرئيسية في جدول تجزئة" ، سأترك رابط هذا بلوق وظيفة, مما يفسر السبب الحقيقي الحالة مثال على المشكلة التي حدثت.

أوصي قراءة كاملة وظيفة, ولكن هنا هو ملخص (التركيز و التوضيحات المضافة).

السبب الافتراضي التجزئة البنيات بطيئة و ليست جيدة جدا:

طريقة CLR تم تصميم كل عضو محدد في System.ValueType أو System.Enum أنواع [قد] يسبب الملاكمة تخصيص [...]

منفذ تجزئة الوظيفة يواجه معضلة:جعل توزيع جيد وظيفة تجزئة أو لجعله سريع.في بعض الحالات من الممكن أن يحقق لهم على حد سواء ، ولكن من الصعب أن تفعل هذا بشكل عام في ValueType.GetHashCode.

الكنسي تجزئة وظيفة البنية "يجمع" تجزئة رموز جميع المجالات.ولكن الطريقة الوحيدة للحصول على رمز التجزئة من حقل في ValueType طريقة استخدام التفكير.لذا CLR الكتاب قررت التجارة السرعة على توزيع الافتراضي GetHashCode الإصدار فقط بإرجاع رمز التجزئة الأولى غير فارغة المجال و "munges" مع نوع معرف [...] هذا هو المعقول سلوك ما.فعلى سبيل المثال ، إذا كنت سيئ الحظ بما فيه الكفاية الحقل الأول من البنية له نفس القيمة بالنسبة لمعظم الحالات ، ثم تجزئة الوظيفة سوف توفر نفس النتيجة في كل وقت.و كما كنت قد يتصور أن هذا سوف يسبب جذرية تأثير الأداء إذا كانت هذه الحالات يتم تخزينها في تجزئة أو مجموعة جدول تجزئة.

[...] التفكير القائم على التنفيذ بطيء.بطيئة جدا.

[...] على حد سواء ValueType.Equals و ValueType.GetHashCode خاصة الأمثل.إذا كان نوع ليس لديها "مؤشرات" و هي معبأة بشكل صحيح [...] ثم الأمثل تستخدم إصدارات: GetHashCode تتكرر على سبيل المثال و XORs كتل من 4 بايت ، Equals طريقة يقارن بين حالتين باستخدام memcmp.[...] ولكن الأمثل هو صعب جدا.أولا من الصعب أن تعرف عندما الأمثل هو تمكين [...] الثانية ، الذاكرة المقارنة بالضرورة تعطيك النتائج الصحيحة.هنا مثال بسيط:[...] -0.0 و +0.0 متساوون ولكن مختلفة ثنائي التمثيل.

في العالم الحقيقي المشكلة الموضحة بعد:

private readonly HashSet<(ErrorLocation, int)> _locationsWithHitCount;
readonly struct ErrorLocation
{
    // Empty almost all the time
    public string OptionalDescription { get; }
    public string Path { get; }
    public int Position { get; }
}

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

لذا للإجابة على السؤال "ما هي الحالات التي يجب أن حزمة بلدي وفي أي الحالات لا يمكن الاعتماد بأمان على تطبيق الافتراضي" على الأقل في حالة البنيات, ، يجب تجاوز Equals و GetHashCode كلما المخصصة الخاصة بك البنية يمكن أن يستخدم كوسيلة رئيسية في جدول تجزئة أو Dictionary.
أود أن أوصي أيضا تنفيذ IEquatable<T> في هذه الحالة لتجنب الملاكمة.

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

بشكل عام، إذا كنت قد تجاوزت المساواة، فأنت ترغب في تجاوز Gethashcode. السبب في ذلك هو أن كلاهما يستخدم لمقارنة المساواة في صفك / بنية.

يستخدم يساوي عند التحقق من foo a، b؛

إذا (a == ب)

منذ أن نعرف أن المؤشر ليس من المرجح أن يتطابق، يمكننا مقارنة الأعضاء الداخليين.

Equals(obj o)
{
    if (o == null) return false;
    MyType Foo = o as MyType;
    if (Foo == null) return false;
    if (Foo.Prop1 != this.Prop1) return false;

    return Foo.Prop2 == this.Prop2;
}

يتم استخدام Gethashcode عموما بواسطة طاولات التجزئة. يجب أن يكون hashcode الناتج عن صفك هو نفسه دائما بالنسبة لفئات تعطي الحالة.

أنا عادة القيام به،

GetHashCode()
{
    int HashCode = this.GetType().ToString().GetHashCode();
    HashCode ^= this.Prop1.GetHashCode();
    etc.

    return HashCode;
}

سيقول البعض أن لا ينبغي احتساب HASHCODE مرة واحدة فقط لكل حياة كائن، لكنني لا أتفق مع ذلك (وأنا ربما أخطأ).

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

إذا كنت تعامل فقط مع Pocos، فيمكنك استخدام هذه الأداة المساعدة لتبسيط حياتك إلى حد ما:

var hash = HashCodeUtil.GetHashCode(
           poco.Field1,
           poco.Field2,
           ...,
           poco.FieldN);

...

public static class HashCodeUtil
{
    public static int GetHashCode(params object[] objects)
    {
        int hash = 13;

        foreach (var obj in objects)
        {
            hash = (hash * 7) + (!ReferenceEquals(null, obj) ? obj.GetHashCode() : 0);
        }

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