سؤال

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

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

المحلول

عادةً ما أختار شيئًا مثل التنفيذ الوارد في Josh Bloch خلاب جافا فعالة.إنه سريع وينشئ تجزئة جيدة جدًا ومن غير المرجح أن تسبب تصادمات.اختر رقمين أوليين مختلفين، على سبيل المثال.17 و 23، وافعل:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}

كما هو مذكور في التعليقات، قد تجد أنه من الأفضل اختيار عدد أولي كبير للضرب به بدلاً من ذلك.على ما يبدو 486187739 جيد ...وعلى الرغم من أن معظم الأمثلة التي رأيتها مع الأعداد الصغيرة تميل إلى استخدام الأعداد الأولية، إلا أن هناك على الأقل خوارزميات مماثلة حيث يتم استخدام الأعداد غير الأولية غالبًا.في ليس تماما-FNV على سبيل المثال، لاحقًا، على سبيل المثال، استخدمت أرقامًا تعمل بشكل جيد على ما يبدو - لكن القيمة الأولية ليست أولية.(ثابت الضرب يكون بالرغم من ذلك.لا أعرف مدى أهمية ذلك.)

وهذا أفضل من الممارسة الشائعة لـ XORاستخدام رموز التجزئة لسببين رئيسيين.لنفترض أن لدينا نوع مع اثنين int مجالات:

XorHash(x, x) == XorHash(y, y) == 0 for all x, y
XorHash(x, y) == XorHash(y, x) for all x, y

بالمناسبة، الخوارزمية السابقة هي التي يستخدمها حاليا مترجم C# للأنواع المجهولة.

هذه الصفحة يعطي عدد لا بأس به من الخيارات.أعتقد أن ما ورد أعلاه في معظم الحالات "جيد بما فيه الكفاية" وأنه من السهل جدًا تذكره وتصحيحه.ال FNV البديل بسيط بالمثل، ولكنه يستخدم ثوابت و XOR بدلاً من ADD كعملية دمج.يبدو شئ ما مثل الكود أدناه، ولكن خوارزمية FNV العادية تعمل على بايتات فردية، لذلك قد يتطلب هذا تعديلًا لإجراء تكرار واحد لكل بايت، بدلاً من كل قيمة تجزئة 32 بت.تم تصميم FNV أيضًا لأطوال مختلفة من البيانات، في حين أن الطريقة التي نستخدمها هنا هي دائمًا لنفس العدد من قيم الحقول.تشير التعليقات على هذه الإجابة إلى أن الكود هنا لا يعمل في الواقع بشكل جيد (في نموذج الحالة الذي تم اختباره) مثل نهج الإضافة أعلاه.

// Note: Not quite FNV!
public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = (int) 2166136261;
        // Suitable nullity checks etc, of course :)
        hash = (hash * 16777619) ^ field1.GetHashCode();
        hash = (hash * 16777619) ^ field2.GetHashCode();
        hash = (hash * 16777619) ^ field3.GetHashCode();
        return hash;
    }
}

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

وفقا ل توثيق:

يمكنك تجاوز GetHashCode لأنواع المراجع غير القابلة للتغيير.بشكل عام، بالنسبة لأنواع المراجع القابلة للتغيير، يجب عليك تجاوز GetHashCode فقط إذا:

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

نصائح أخرى

تحليل مجهول نوع

مايكروسوفت توفر بالفعل جيدة مولد شفرة التجزئة العامة: مجرد نسخ قيمة العقارات / مجال عملك إلى نوع مجهول وبعثرة ما يلي:

new { PropA, PropB, PropC, PropD }.GetHashCode();

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

تحليل ValueTuple - تحديث لC # 7

وكماcactuaroid يذكر في التعليقات، والصفوف (tuple) قيمة يمكن استخدامها. وهذا يوفر بضع ضربات المفاتيح، والأهم من ينفذ بحتة على المكدس (لا القمامة):

(PropA, PropB, PropC, PropD).GetHashCode();

و(ملاحظة: هذه التقنية الأصلي باستخدام أنواع مجهولة يبدو لخلق كائن على كومة، أي القمامة، حيث يتم تنفيذ أنواع مجهولة كما الطبقات، ورغم أن هذا قد يكون الأمثل من قبل المترجم وسيكون من المثير للاهتمام أن مؤشر هذه الخيارات. ، ولكن الخيار الصفوف (tuple) يجب أن تكون متفوقة.)

وهنا هو بلدي المساعد شفرة التجزئة.
انها ميزة هو أنه يستخدم الحجج نوع عام، وبالتالي لن يسبب الملاكمة:

public static class HashHelper
{
    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
         unchecked
         {
             return 31 * arg1.GetHashCode() + arg2.GetHashCode();
         }
    }

    public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            return 31 * hash + arg3.GetHashCode();
        }
    }

    public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, 
        T4 arg4)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            hash = 31 * hash + arg3.GetHashCode();
            return 31 * hash + arg4.GetHashCode();
        }
    }

    public static int GetHashCode<T>(T[] list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    public static int GetHashCode<T>(IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    /// <summary>
    /// Gets a hashcode for a collection for that the order of items 
    /// does not matter.
    /// So {1, 2, 3} and {3, 2, 1} will get same hash code.
    /// </summary>
    public static int GetHashCodeForOrderNoMatterCollection<T>(
        IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            int count = 0;
            foreach (var item in list)
            {
                hash += item.GetHashCode();
                count++;
            }
            return 31 * hash + count.GetHashCode();
        }
    }

    /// <summary>
    /// Alternative way to get a hashcode is to use a fluent 
    /// interface like this:<br />
    /// return 0.CombineHashCode(field1).CombineHashCode(field2).
    ///     CombineHashCode(field3);
    /// </summary>
    public static int CombineHashCode<T>(this int hashCode, T arg)
    {
        unchecked
        {
            return 31 * hashCode + arg.GetHashCode();   
        }
    }

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

public override int GetHashCode()
{
    return HashHelper.GetHashCode(Manufacturer, PartN, Quantity);
}

وأو من هذا القبيل:

public override int GetHashCode()
{
    return 0.CombineHashCode(Manufacturer)
        .CombineHashCode(PartN)
        .CombineHashCode(Quantity);
}

ولدي فئة تجزئة في مكتبة مساعد التي تستخدم لهذا الغرض.

/// <summary> 
/// This is a simple hashing function from Robert Sedgwicks Hashing in C book.
/// Also, some simple optimizations to the algorithm in order to speed up
/// its hashing process have been added. from: www.partow.net
/// </summary>
/// <param name="input">array of objects, parameters combination that you need
/// to get a unique hash code for them</param>
/// <returns>Hash code</returns>
public static int RSHash(params object[] input)
{
    const int b = 378551;
    int a = 63689;
    int hash = 0;

    // If it overflows then just wrap around
    unchecked
    {
        for (int i = 0; i < input.Length; i++)
        {
            if (input[i] != null)
            {
                hash = hash * a + input[i].GetHashCode();
                a = a * b;
            }
        }
    }

    return hash;
}

وبعد ذلك، ببساطة يمكنك استخدامه على النحو التالي:

public override int GetHashCode()
{
    return Hashing.RSHash(_field1, _field2, _field3);
}

لم أكن تقييم أدائها، وبالتالي هو موضع ترحيب أي ردود فعل.

ها هي فئة المساعد الخاصة بي باستخدام تنفيذ جون سكيت.

public static class HashCode
{
    public const int Start = 17;

    public static int Hash<T>(this int hash, T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked((hash * 31) + h);
    }
}

الاستخدام:

public override int GetHashCode()
{
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)
        .Hash(_field3);
}

إذا كنت تريد تجنب كتابة طريقة ملحق لـ System.Int32:

public struct HashCode
{
    private readonly int _value;

    public HashCode(int value) => _value = value;

    public static HashCode Start { get; } = new HashCode(17);

    public static implicit operator int(HashCode hash) => hash._value;

    public HashCode Hash<T>(T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked(new HashCode((_value * 31) + h));
    }

    public override int GetHashCode() => _value;
}

لا يزال الأمر عامًا، ولا يزال يتجنب أي تخصيص للكومة ويتم استخدامه بنفس الطريقة تمامًا:

public override int GetHashCode()
{
    // This time `HashCode.Start` is not an `Int32`, it's a `HashCode` instance.
    // And the result is implicitly converted to `Int32`.
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)     
        .Hash(_field3);
}

تحديث بعد تعليق مارتن:

obj != null تسببت في الملاكمة لذلك قمت بالتبديل إلى المقارنة الافتراضية.

تحرير (مايو 2018):

EqualityComparer<T>.Default أصبح getter الآن جزءًا جوهريًا من JIT - the طلب سحب ذكره ستيفن توب في هذا بلوق وظيفة.

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

يجب أن يكون الرفع الثقيل جزءًا من طريقة Equals();يجب أن تكون عملية التجزئة رخيصة جدًا لتمكين استدعاء Equals() على أقل عدد ممكن من العناصر.

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

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

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

public static int ReHash(int source)
{
  unchecked
  {
    ulong c = 0xDEADBEEFDEADBEEF + (ulong)source;
    ulong d = 0xE2ADBEEFDEADBEEF ^ c;
    ulong a = d += c = c << 15 | c >> -15;
    ulong b = a += d = d << 52 | d >> -52;
    c ^= b += a = a << 26 | a >> -26;
    d ^= c += b = b << 51 | b >> -51;
    a ^= d += c = c << 28 | c >> -28;
    b ^= a += d = d << 9 | d >> -9;
    c ^= b += a = a << 47 | a >> -47;
    d ^= c += b << 54 | b >> -54;
    a ^= d += c << 32 | c >> 32;
    a += d << 25 | d >> -25;
    return (int)(a >> 1);
  }
}

ومن ثم لم يعد جدول التجزئة الخاص بي سيئًا بعد الآن.

لكن هذا أزعجني، لأن ما سبق لا ينبغي أن يعمل.أو بتعبير أدق، لا ينبغي أن تعمل إلا إذا كانت أصلية GetHashCode() كان فقيرا بطريقة خاصة جدا.

لا يمكن لإعادة خلط رمز التجزئة تحسين رمز التجزئة الرائع، لأن التأثير الوحيد الممكن هو أننا نقدم المزيد من التصادمات.

إعادة خلط رمز التجزئة لا يمكن أن يؤدي إلى تحسين رمز التجزئة الرهيب، لأن التأثير الوحيد الممكن هو أن نغير على سبيل المثال.عدد كبير من الاصطدامات على القيمة 53 لعدد كبير من القيمة 18,3487,291.

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

يحرر:كنت أستخدم أيضًا المخاطبة المفتوحة، والتي كان من شأنها أيضًا أن تزيد من حساسية الاصطدام، وربما أكثر من حقيقة أنها كانت قوة اثنين.

حسنًا، كان الأمر مزعجًا إلى أي مدى string.GetHashCode() التنفيذ في .شبكة (او الدراسة هنا) بهذه الطريقة (حسب ترتيب الاختبارات التي يتم إجراؤها بشكل أسرع بحوالي 20 إلى 30 مرة بسبب عدد أقل من الاصطدامات) والأكثر إثارة للقلق هو مدى إمكانية تحسين رموز التجزئة الخاصة بي (أكثر من ذلك بكثير).

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

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

في النهاية استقرت على النقل SpookyHash إلى الشباك.في الواقع، الكود أعلاه هو إصدار سريع المسار لاستخدام SpookyHash لإنتاج مخرجات 32 بت من مدخلات 32 بت.

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

ثم أضع الذي - التي جانب واحد من المشروع، لأنه مثلما طرح المشروع الأصلي سؤالًا حول كيفية إنتاج كود تجزئة أفضل، فقد طرح هذا المشروع سؤالًا حول كيفية إنتاج .NET memcpy أفضل.

ثم عدت، وأنتجت الكثير من الأحمال الزائدة لتغذية جميع الأنواع الأصلية تقريبًا (باستثناء decimal†) في رمز التجزئة.

إنه سريع، وهو ما يستحق بوب جينكينز معظم الفضل فيه لأن كوده الأصلي الذي قمت بالنقل منه لا يزال أسرع، خاصة على أجهزة 64 بت التي تم تحسين الخوارزمية من أجلها‡.

يمكن رؤية الكود الكامل على https://bitbucket.org/JonHanna/spookilysharp/src ولكن ضع في اعتبارك أن الكود أعلاه هو نسخة مبسطة منه.

ومع ذلك، نظرًا لأنه مكتوب بالفعل، يمكن للمرء الاستفادة منه بسهولة أكبر:

public override int GetHashCode()
{
  var hash = new SpookyHash();
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

يتطلب الأمر أيضًا قيمًا أولية، لذلك إذا كنت بحاجة إلى التعامل مع مدخلات غير موثوقة وترغب في الحماية من هجمات Hash DoS، فيمكنك تعيين بذرة بناءً على وقت التشغيل أو ما شابه، وجعل النتائج غير قابلة للتنبؤ من قبل المهاجمين:

private static long hashSeed0 = Environment.TickCount;
private static long hashSeed1 = DateTime.Now.Ticks;
public override int GetHashCode()
{
  //produce different hashes ever time this application is restarted
  //but remain consistent in each run, so attackers have a harder time
  //DoSing the hash tables.
  var hash = new SpookyHash(hashSeed0, hashSeed1);
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

*المفاجأة الكبرى في هذا هي إعادة التضمين اليدوي لطريقة التدوير (x << n) | (x >> -n) أشياء محسنة.كنت على يقين من أن التوتر كان سيوضح ذلك بالنسبة لي، لكن التنميط أظهر خلاف ذلك.

decimal ليس أصليًا من منظور .NET على الرغم من أنه من C#.المشكلة فيه هي أنه خاص به GetHashCode() يعامل الدقة على أنها مهمة في حين أنها خاصة بها Equals() لا.كلاهما خياران صالحان، لكن ليسا مختلطين بهذه الطريقة.عند تنفيذ الإصدار الخاص بك، يتعين عليك اختيار تنفيذ أحدهما أو الآخر، لكن لا يمكنني معرفة أيهما تريد.

‡على سبيل المقارنة.إذا تم استخدامه على سلسلة، فإن SpookyHash على 64 بت يكون أسرع بكثير من string.GetHashCode() على 32 بت وهو أسرع قليلاً من string.GetHashCode() على 64 بت، وهو أسرع بكثير من SpookyHash على 32 بت، على الرغم من أنه لا يزال سريعًا بما يكفي ليكون خيارًا معقولاً.

وهذا أمر جيد واحد:

/// <summary>
/// Helper class for generating hash codes suitable 
/// for use in hashing algorithms and data structures like a hash table. 
/// </summary>
public static class HashCodeHelper
{
    private static int GetHashCodeInternal(int key1, int key2)
    {
        unchecked
        {
           var num = 0x7e53a269;
           num = (-1521134295 * num) + key1;
           num += (num << 10);
           num ^= (num >> 6);

           num = ((-1521134295 * num) + key2);
           num += (num << 10);
           num ^= (num >> 6);

           return num;
        }
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="arr">An array of objects used for generating the 
    /// hash code.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode(params object[] arr)
    {
        int hash = 0;
        foreach (var item in arr)
            hash = GetHashCodeInternal(hash, item.GetHashCode());
        return hash;
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <param name="obj4">The fourth object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and
    /// data structures like a hash table.
    /// </returns>
    public static int GetHashCode<T1, T2, T3, T4>(T1 obj1, T2 obj2, T3 obj3,
        T4 obj4)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3, obj4));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2, T3>(T1 obj1, T2 obj2, T3 obj3)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2>(T1 obj1, T2 obj2)
    {
        return GetHashCodeInternal(obj1.GetHashCode(), obj2.GetHashCode());
    }
}

وهنا هو كيفية استخدامه:

private struct Key
{
    private Type _type;
    private string _field;

    public Type Type { get { return _type; } }
    public string Field { get { return _field; } }

    public Key(Type type, string field)
    {
        _type = type;
        _field = field;
    }

    public override int GetHashCode()
    {
        return HashCodeHelper.GetHashCode(_field, _type);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Key))
            return false;
        var tf = (Key)obj;
        return tf._field.Equals(_field) && tf._type.Equals(_type);
    }
}

وهنا هو تنفيذ بطلاقة آخر من خوارزمية نشرت أعلاه من قبل جون السكيت ، ولكن الذي لا يتضمن أي مخصصات أو عمليات الملاكمة:

public static class Hash
{
    public const int Base = 17;

    public static int HashObject(this int hash, object obj)
    {
        unchecked { return hash * 23 + (obj == null ? 0 : obj.GetHashCode()); }
    }

    public static int HashValue<T>(this int hash, T value)
        where T : struct
    {
        unchecked { return hash * 23 + value.GetHashCode(); }
    }
}

والاستعمال:

public class MyType<T>
{
    public string Name { get; set; }

    public string Description { get; set; }

    public int Value { get; set; }

    public IEnumerable<T> Children { get; set; }

    public override int GetHashCode()
    {
        return Hash.Base
            .HashObject(this.Name)
            .HashObject(this.Description)
            .HashValue(this.Value)
            .HashObject(this.Children);
    }
}

لا يسمى مترجم سيضمن HashValue مع فئة ويرجع ذلك إلى نوع القيد عام. ولكن لا يوجد أي دعم مترجم لHashObject منذ مضيفا حجة عامة يضيف أيضا عملية الملاكمة.

https://github.com/dotnet/coreclr/pull/14863، هناك طريقة جديدة لتوليد رموز التجزئة التي هو السوبر بسيط! اكتبوا

public override int GetHashCode()
    => HashCode.Combine(field1, field2, field3);

وهذا سيولد رمز الجودة التجزئة دون الحاجة إلى القلق بشأن تفاصيل التنفيذ.

وهنا هو بلدي نهج التبسيط. أنا باستخدام نمط البناء التقليدي لهذا الغرض. ومن typesafe (لا الملاكمة / علبته) وcompatbile أيضا مع الصافي 2.0 (أي طرق الإرشاد الخ.).

ويستخدم مثل هذا:

public override int GetHashCode()
{
    HashBuilder b = new HashBuilder();
    b.AddItems(this.member1, this.member2, this.member3);
    return b.Result;
} 

وهنا هي الطبقة بناء على acutal:

internal class HashBuilder
{
    private const int Prime1 = 17;
    private const int Prime2 = 23;
    private int result = Prime1;

    public HashBuilder()
    {
    }

    public HashBuilder(int startHash)
    {
        this.result = startHash;
    }

    public int Result
    {
        get
        {
            return this.result;
        }
    }

    public void AddItem<T>(T item)
    {
        unchecked
        {
            this.result = this.result * Prime2 + item.GetHashCode();
        }
    }

    public void AddItems<T1, T2>(T1 item1, T2 item2)
    {
        this.AddItem(item1);
        this.AddItem(item2);
    }

    public void AddItems<T1, T2, T3>(T1 item1, T2 item2, T3 item3)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
    }

    public void AddItems<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, 
        T4 item4)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
    }

    public void AddItems<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, 
        T4 item4, T5 item5)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
        this.AddItem(item5);
    }        

    public void AddItems<T>(params T[] items)
    {
        foreach (T item in items)
        {
            this.AddItem(item);
        }
    }
}

يمكن للمستخدمين ReSharper توليد GetHashCode، يساوي، وغيرهم من ذوي ReSharper -> Edit -> Generate Code -> Equality Members.

// ReSharper's GetHashCode looks like this
public override int GetHashCode() {
    unchecked {
        int hashCode = Id;
        hashCode = (hashCode * 397) ^ IntMember;
        hashCode = (hashCode * 397) ^ OtherIntMember;
        hashCode = (hashCode * 397) ^ (RefMember != null ? RefMember.GetHashCode() : 0);
        // ...
        return hashCode;
    }
}

ويتم معظم عملي مع اتصال قاعدة البيانات وهو ما يعني أن دروسي جميعا معرف فريد من قاعدة البيانات. أنا دائما استخدام ID من قاعدة البيانات لتوليد شفرة التجزئة.

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}

والى حد كبير على غرار الحل nightcoder ما عدا أنه من الأسهل لجمع الأعداد الأولية إذا كنت ترغب في ذلك.

وPS: هذا هو واحد من تلك الأوقات التي كنت تقيؤ قليلا في فمك، مع العلم أن هذا يمكن ريفاكتوريد إلى أسلوب واحد مع 9 الافتراضي ولكن سيكون أبطأ، لذلك أنت فقط تغمض عينيك ومحاولة نسيانها .

/// <summary>
/// Try not to look at the source code. It works. Just rely on it.
/// </summary>
public static class HashHelper
{
    private const int PrimeOne = 17;
    private const int PrimeTwo = 23;

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();
            hash = hash * PrimeTwo + arg10.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();

            return hash;
        }
    }
}

إذا لم يكن لدينا أكثر من 8 خصائص (نأمل)، فإليك بديل آخر.

ValueTuple هو هيكل ويبدو أن لديه مادة صلبة GetHashCode تطبيق.

وهذا يعني أنه يمكننا ببساطة القيام بذلك:

// Yay, no allocations and no custom implementations!
public override int GetHashCode() => (this.PropA, this.PropB).GetHashCode();

دعونا نلقي نظرة على التنفيذ الحالي لـ .NET Core لـ ValueTupleGetHashCode.

هذا من ValueTuple:

    internal static int CombineHashCodes(int h1, int h2)
    {
        return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2);
    }

    internal static int CombineHashCodes(int h1, int h2, int h3)
    {
        return HashHelpers.Combine(CombineHashCodes(h1, h2), h3);
    }

وهذا من HashHelper:

    public static readonly int RandomSeed = Guid.NewGuid().GetHashCode();

    public static int Combine(int h1, int h2)
    {
        unchecked
        {
            // RyuJIT optimizes this to use the ROL instruction
            // Related GitHub pull request: dotnet/coreclr#1830
            uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
            return ((int)rol5 + h1) ^ h2;
        }
    }

باللغة الإنجليزية:

  • تدوير لليسار (إزاحة دائرية) h1 بمقدار 5 مواضع.
  • أضف النتيجة وh1 معًا.
  • XOR النتيجة مع h2.
  • ابدأ بإجراء العملية المذكورة أعلاه على { static Random Seed, h1 }.
  • لكل عنصر آخر، قم بإجراء العملية على النتيجة السابقة والعنصر التالي (على سبيل المثال.ح2).

سيكون من الجيد معرفة المزيد عن خصائص خوارزمية رمز التجزئة ROL-5.

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

. NET الأساسية 2.1 فما فوق

إذا كنت تستخدم. NET الأساسية 2.1 أو أعلى، يمكنك استخدام في System.HashCode البنية. هناك طريقتان لاستخدامه:

HashCode.Combine

ويمكن استخدام طريقة Combine لإنشاء رمز التجزئة، نظرا يصل إلى ثمانية الكائنات.

public override int GetHashCode() => HashCode.Combine(this.object1, this.object2);

HashCode.Add

وطريقة Add يساعدك على التعامل مع مجموعات:

public override int GetHashCode()
{
    var hashCode = new HashCode();
    hashCode.Add(this.object1);
    foreach (var item in this.collection)
    {
        hashCode.Add(item);
    }
    return hashCode.ToHashCode();
}

GetHashCode سهلة

ويمكنك قراءة بلوق وظيفة كاملة " GetHashCode سهلة " لمزيد من التفاصيل و التعليقات.

الاستخدام مثال

public class SuperHero
{
    public int Age { get; set; }
    public string Name { get; set; }
    public List<string> Powers { get; set; }

    public override int GetHashCode() =>
        HashCode.Of(this.name).And(this.age).AndEach(this.powers);
}

التنفيذ

public struct HashCode : IEquatable<HashCode>
{
    private const int EmptyCollectionPrimeNumber = 19;
    private readonly int value;

    private HashCode(int value) => this.value = value;

    public static implicit operator int(HashCode hashCode) => hashCode.value;

    public static bool operator ==(HashCode left, HashCode right) => left.Equals(right);

    public static bool operator !=(HashCode left, HashCode right) => !(left == right);

    public static HashCode Of<T>(T item) => new HashCode(GetHashCode(item));

    public static HashCode OfEach<T>(IEnumerable<T> items) =>
        items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0));

    public HashCode And<T>(T item) => 
        new HashCode(CombineHashCodes(this.value, GetHashCode(item)));

    public HashCode AndEach<T>(IEnumerable<T> items)
    {
        if (items == null)
        {
            return new HashCode(this.value);
        }

        return new HashCode(GetHashCode(items, this.value));
    }

    public bool Equals(HashCode other) => this.value.Equals(other.value);

    public override bool Equals(object obj)
    {
        if (obj is HashCode)
        {
            return this.Equals((HashCode)obj);
        }

        return false;
    }

    public override int GetHashCode() => this.value.GetHashCode();

    private static int CombineHashCodes(int h1, int h2)
    {
        unchecked
        {
            // Code copied from System.Tuple a good way to combine hashes.
            return ((h1 << 5) + h1) ^ h2;
        }
    }

    private static int GetHashCode<T>(T item) => item?.GetHashCode() ?? 0;

    private static int GetHashCode<T>(IEnumerable<T> items, int startHashCode)
    {
        var temp = startHashCode;

        var enumerator = items.GetEnumerator();
        if (enumerator.MoveNext())
        {
            temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));

            while (enumerator.MoveNext())
            {
                temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));
            }
        }
        else
        {
            temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber);
        }

        return temp;
    }
}

وأنا واجهت مشكلة مع عوامات والكسور العشرية باستخدام تنفيذ اختياره الجواب أعلاه.

وهذا الاختبار فشل (العوامات، التجزئة هي نفسها على الرغم من أنني تحولت 2 القيم أن تكون سلبية):

        var obj1 = new { A = 100m, B = 100m, C = 100m, D = 100m};
        var obj2 = new { A = 100m, B = 100m, C = -100m, D = -100m};
        var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D);
        var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D);
        Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different   hash1:{0}  hash2:{1}",hash1,hash2));

ولكن يمر هذا الاختبار (مع [إينتس]):

        var obj1 = new { A = 100m, B = 100m, C = 100, D = 100};
        var obj2 = new { A = 100m, B = 100m, C = -100, D = -100};
        var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D);
        var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D);
        Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different   hash1:{0}  hash2:{1}",hash1,hash2));

ولقد غيرت تنفيذ بلدي لعدم استخدام GetHashCode لأنواع بدائية ويبدو للعمل بشكل أفضل

    private static int InternalComputeHash(params object[] obj)
    {
        unchecked
        {
            var result = (int)SEED_VALUE_PRIME;
            for (uint i = 0; i < obj.Length; i++)
            {
                var currval = result;
                var nextval = DetermineNextValue(obj[i]);
                result = (result * MULTIPLIER_VALUE_PRIME) + nextval;

            }
            return result;
        }
    }



    private static int DetermineNextValue(object value)
    {
        unchecked
        {

                int hashCode;
                if (value is short
                    || value is int
                    || value is byte
                    || value is sbyte
                    || value is uint
                    || value is ushort
                    || value is ulong
                    || value is long
                    || value is float
                    || value is double
                    || value is decimal)
                {
                    return Convert.ToInt32(value);
                }
                else
                {
                    return value != null ? value.GetHashCode() : 0;
                }
        }
    }

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

ويمكنك تمرير مقارنة سلسلة يطابق تنفيذ متساوين الخاص بك.

ولأن الناتج تجزئة دائما عدد صحيح، يمكنك فقط سلسلة دعوات التجزئة.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;


namespace Sc.Util.System
{
    /// <summary>
    /// Static methods that allow easy implementation of hashCode. Example usage:
    /// <code>
    /// public override int GetHashCode()
    ///     => HashCodeHelper.Seed
    ///         .Hash(primitiveField)
    ///         .Hsh(objectField)
    ///         .Hash(iEnumerableField);
    /// </code>
    /// </summary>
    public static class HashCodeHelper
    {
        /// <summary>
        /// An initial value for a hashCode, to which is added contributions from fields.
        /// Using a non-zero value decreases collisions of hashCode values.
        /// </summary>
        public const int Seed = 23;

        private const int oddPrimeNumber = 37;


        /// <summary>
        /// Rotates the seed against a prime number.
        /// </summary>
        /// <param name="aSeed">The hash's first term.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int rotateFirstTerm(int aSeed)
        {
            unchecked {
                return HashCodeHelper.oddPrimeNumber * aSeed;
            }
        }


        /// <summary>
        /// Contributes a boolean to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aBoolean">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, bool aBoolean)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + (aBoolean
                                ? 1
                                : 0);
            }
        }

        /// <summary>
        /// Contributes a char to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aChar">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, char aChar)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + aChar;
            }
        }

        /// <summary>
        /// Contributes an int to the developing HashCode seed.
        /// Note that byte and short are handled by this method, through implicit conversion.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aInt">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, int aInt)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + aInt;
            }
        }

        /// <summary>
        /// Contributes a long to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aLong">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, long aLong)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + (int)(aLong ^ (aLong >> 32));
            }
        }

        /// <summary>
        /// Contributes a float to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aFloat">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, float aFloat)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + Convert.ToInt32(aFloat);
            }
        }

        /// <summary>
        /// Contributes a double to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aDouble">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, double aDouble)
            => aSeed.Hash(Convert.ToInt64(aDouble));

        /// <summary>
        /// Contributes a string to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aString">The value to contribute.</param>
        /// <param name="stringComparison">Optional comparison that creates the hash.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(
                this int aSeed,
                string aString,
                StringComparison stringComparison = StringComparison.Ordinal)
        {
            if (aString == null)
                return aSeed.Hash(0);
            switch (stringComparison) {
                case StringComparison.CurrentCulture :
                    return StringComparer.CurrentCulture.GetHashCode(aString);
                case StringComparison.CurrentCultureIgnoreCase :
                    return StringComparer.CurrentCultureIgnoreCase.GetHashCode(aString);
                case StringComparison.InvariantCulture :
                    return StringComparer.InvariantCulture.GetHashCode(aString);
                case StringComparison.InvariantCultureIgnoreCase :
                    return StringComparer.InvariantCultureIgnoreCase.GetHashCode(aString);
                case StringComparison.OrdinalIgnoreCase :
                    return StringComparer.OrdinalIgnoreCase.GetHashCode(aString);
                default :
                    return StringComparer.Ordinal.GetHashCode(aString);
            }
        }

        /// <summary>
        /// Contributes a possibly-null array to the developing HashCode seed.
        /// Each element may be a primitive, a reference, or a possibly-null array.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aArray">CAN be null.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, IEnumerable aArray)
        {
            if (aArray == null)
                return aSeed.Hash(0);
            int countPlusOne = 1; // So it differs from null
            foreach (object item in aArray) {
                ++countPlusOne;
                if (item is IEnumerable arrayItem) {
                    if (!object.ReferenceEquals(aArray, arrayItem))
                        aSeed = aSeed.Hash(arrayItem); // recursive call!
                } else
                    aSeed = aSeed.Hash(item);
            }
            return aSeed.Hash(countPlusOne);
        }

        /// <summary>
        /// Contributes a possibly-null array to the developing HashCode seed.
        /// You must provide the hash function for each element.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aArray">CAN be null.</param>
        /// <param name="hashElement">Required: yields the hash for each element
        /// in <paramref name="aArray"/>.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash<T>(this int aSeed, IEnumerable<T> aArray, Func<T, int> hashElement)
        {
            if (aArray == null)
                return aSeed.Hash(0);
            int countPlusOne = 1; // So it differs from null
            foreach (T item in aArray) {
                ++countPlusOne;
                aSeed = aSeed.Hash(hashElement(item));
            }
            return aSeed.Hash(countPlusOne);
        }

        /// <summary>
        /// Contributes a possibly-null object to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aObject">CAN be null.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, object aObject)
        {
            switch (aObject) {
                case null :
                    return aSeed.Hash(0);
                case bool b :
                    return aSeed.Hash(b);
                case char c :
                    return aSeed.Hash(c);
                case int i :
                    return aSeed.Hash(i);
                case long l :
                    return aSeed.Hash(l);
                case float f :
                    return aSeed.Hash(f);
                case double d :
                    return aSeed.Hash(d);
                case string s :
                    return aSeed.Hash(s);
                case IEnumerable iEnumerable :
                    return aSeed.Hash(iEnumerable);
            }
            return aSeed.Hash(aObject.GetHashCode());
        }


        /// <summary>
        /// This utility method uses reflection to iterate all specified properties that are readable
        /// on the given object, excluding any property names given in the params arguments, and
        /// generates a hashcode.
        /// </summary>
        /// <param name="aSeed">The developing hash code, or the seed: if you have no seed, use
        /// the <see cref="Seed"/>.</param>
        /// <param name="aObject">CAN be null.</param>
        /// <param name="propertySelector"><see cref="BindingFlags"/> to select the properties to hash.</param>
        /// <param name="ignorePropertyNames">Optional.</param>
        /// <returns>A hash from the properties contributed to <c>aSeed</c>.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashAllProperties(
                this int aSeed,
                object aObject,
                BindingFlags propertySelector
                        = BindingFlags.Instance
                        | BindingFlags.Public
                        | BindingFlags.GetProperty,
                params string[] ignorePropertyNames)
        {
            if (aObject == null)
                return aSeed.Hash(0);
            if ((ignorePropertyNames != null)
                    && (ignorePropertyNames.Length != 0)) {
                foreach (PropertyInfo propertyInfo in aObject.GetType()
                        .GetProperties(propertySelector)) {
                    if (!propertyInfo.CanRead
                            || (Array.IndexOf(ignorePropertyNames, propertyInfo.Name) >= 0))
                        continue;
                    aSeed = aSeed.Hash(propertyInfo.GetValue(aObject));
                }
            } else {
                foreach (PropertyInfo propertyInfo in aObject.GetType()
                        .GetProperties(propertySelector)) {
                    if (propertyInfo.CanRead)
                        aSeed = aSeed.Hash(propertyInfo.GetValue(aObject));
                }
            }
            return aSeed;
        }


        /// <summary>
        /// NOTICE: this method is provided to contribute a <see cref="KeyValuePair{TKey,TValue}"/> to
        /// the developing HashCode seed; by hashing the key and the value independently. HOWEVER,
        /// this method has a different name since it will not be automatically invoked by
        /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>,
        /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless
        /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise
        /// the generated hash code will not be consistent. This method itself ALSO will not invoke
        /// this method on the Key or Value here if that itself is a KeyValuePair.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="keyValuePair">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashKeyAndValue<TKey, TValue>(this int aSeed, KeyValuePair<TKey, TValue> keyValuePair)
            => aSeed.Hash(keyValuePair.Key)
                    .Hash(keyValuePair.Value);

        /// <summary>
        /// NOTICE: this method is provided to contribute a collection of <see cref="KeyValuePair{TKey,TValue}"/>
        /// to the developing HashCode seed; by hashing the key and the value independently. HOWEVER,
        /// this method has a different name since it will not be automatically invoked by
        /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>,
        /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless
        /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise
        /// the generated hash code will not be consistent. This method itself ALSO will not invoke
        /// this method on a Key or Value here if that itself is a KeyValuePair or an Enumerable of
        /// KeyValuePair.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="keyValuePairs">The values to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashKeysAndValues<TKey, TValue>(
                this int aSeed,
                IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs)
        {
            if (keyValuePairs == null)
                return aSeed.Hash(null);
            foreach (KeyValuePair<TKey, TValue> keyValuePair in keyValuePairs) {
                aSeed = aSeed.HashKeyAndValue(keyValuePair);
            }
            return aSeed;
        }
    }
}

ومايكروسوفت تقدم العديد من طريقة للتجزئة ...

//for classes that contain a single int value
return this.value;

//for classes that contain multiple int value
return x ^ y;

//for classes that contain single number bigger than int    
return ((int)value ^ (int)(value >> 32)); 

//for classes that contain class instance fields which inherit from object
return obj1.GetHashCode();

//for classes that contain multiple class instance fields which inherit from object
return obj1.GetHashCode() ^ obj2.GetHashCode() ^ obj3.GetHashCode(); 

وأستطيع أن أخمن أنه بالنسبة لمضاعفات كبيرة كثافة العمليات يمكنك استخدام هذا:

int a=((int)value1 ^ (int)(value1 >> 32));
int b=((int)value2 ^ (int)(value2 >> 32));
int c=((int)value3 ^ (int)(value3 >> 32));
return a ^ b ^ c;

ونفسها من أجل متعددة من نوع: كل تحويلها أولا إلى int باستخدام GetHashCode() ثم سيتم xor'ed القيم int و النتيجة هي التجزئة الخاصة بك.

وبالنسبة لأولئك الذين يستخدمون التجزئة كما ID (يعني قيمة فريدة من نوعها)، وتجزئة تقتصر بطبيعة الحال إلى عدد من الأرقام، وأعتقد أنه كان 5 بايت للتجزئة الخوارزمية، على الأقل MD5.

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

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