Runtime: إضافة نوع HashCode للمساعدة في دمج أكواد التجزئة

تم إنشاؤها على ٢٥ أبريل ٢٠١٦  ·  206تعليقات  ·  مصدر: dotnet/runtime

استبدال المناقشة الطويلة بأكثر من 200 تعليق

هذا الموضوع مغلق !!!


تحفيز

تحتوي Java على Objects.hash للجمع السريع بين أكواد التجزئة للحقول المكونة لإرجاعها في Object.hashCode() . للأسف، وصافي لا يوجد لديه مثل هذا يعادل ويضطرون للمطورين للفة التجزئة الخاصة بها مثل هذا :

public override int GetHashCode()
{
    unchecked
    {
        int result = 17;
        result = result * 23 + field1.GetHashCode();
        result = result * 23 + field2.GetHashCode();
        return result;
    }
}

في بعض الأحيان يلجأ الناس إلى استخدام Tuple.Create(field1, field2, ...).GetHashCode() لهذا الغرض ، وهو أمر سيء (من الواضح) لأنه يخصص.

عرض

  • قائمة التغييرات في الاقتراح الحالي (مقابل آخر إصدار معتمد https://github.com/dotnet/corefx/issues/8034#issuecomment-262331783):

    • تمت إضافة خاصية Empty (كنقطة بداية طبيعية مماثلة لـ ImmutableArray )

    • تم تحديث أسماء الوسيطات: hash -> hashCode ، obj -> item

namespace System
{
    public struct HashCode : IEquatable<HashCode>
    {
        public HashCode();

        public static HashCode Empty { get; }

        public static HashCode Create(int hashCode);
        public static HashCode Create<T>(T item);
        public static HashCode Create<T>(T item, IEqualityComparer<T> comparer);

        public HashCode Combine(int hashCode);
        public HashCode Combine<T>(T item);
        public HashCode Combine<T>(T item, IEqualityComparer<T> comparer);

        public int Value { get; }

        public static implicit operator int(HashCode hashCode);

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

        public bool Equals(HashCode other);
        public override bool Equals(object obj);
        public override int GetHashCode();
        public override string ToString();
    }
}

استعمال:

ج #
int hashCode1 = HashCode.Create (f1) .Combine (f2) .Value ؛
int hashCode2 = hashes.Aggregate (HashCode.Empty، (seed، hash) => seed.Combine (hash)) ؛

var hashCode3 = HashCode.Empty ،
foreach (تجزئة int في التجزئة) {hashCode3 = hashCode3.Combine (hash) ؛ }
(int) hashCode3 ؛
""

ملحوظات

يجب أن يستخدم التنفيذ الخوارزمية في HashHelpers .

Design Discussion api-needs-work area-System.Numerics

التعليق الأكثر فائدة

[redknightlois] إذا كان ما نحتاج إليه هو سبب منطقي للذهاب إلى System يمكنني تجربة التبرير. لقد أنشأنا HashCode للمساعدة في تنفيذ object.GetHashCode() ، ويبدو أنه من المناسب أن يشترك كلاهما في مساحة الاسم.

كان هذا هو الأساس المنطقي الذي استخدمناه أنا و

ال 206 كومينتر

إذا كنت تريد شيئًا سريعًا وقذرًا ، فيمكنك استخدام ValueTuple.Create(field1, field2).GetHashCode() . إنها نفس الخوارزمية المستخدمة في Tuple (والتي تعتبر مشابهة لتلك الموجودة في Objects ) ولا تحتوي على النفقات العامة للتخصيص.

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

JonHanna أعتقد أن هذا السؤال ينطبق أيضًا ، على سبيل المثال ، string.GetHashCode() . لا أفهم لماذا يجب أن يكون تقديم Hash أصعب من ذلك.

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

+1

لدينا واحدة من هذه في ASP.NET ، https://github.com/aspnet/Common/blob/dev/src/Microsoft.Extensions.HashCodeCombiner.Sources/HashCodeCombiner.cs. كما أنها ودية مضمنة.

إذا كنت تريد شيئًا سريعًا وقذرًا ، فيمكنك استخدام ValueTuple.Create (field1 ، field2) .GetHashCode ().

آه ، فكرة جيدة ، لم أفكر في ValueTuple عند إنشاء هذا المنشور. لسوء الحظ ، لا أعتقد أن ذلك سيكون متاحًا حتى C # 7 / إصدار إطار العمل التالي ، أو حتى معرفة ما إذا كان سيكون ذا الأداء الجيد (يمكن أن تضيف مكالمات الخاصية / الطريقة هذه إلى EqualityComparer ). لكني لم آخذ أي معايير لقياس هذا ، لذلك لن أعرف حقًا. أعتقد فقط أنه يجب أن يكون هناك فئة مخصصة / بسيطة للتجزئة التي يمكن للأشخاص استخدامها دون استخدام tuples كحل بديل.

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

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

لسوء الحظ ، لا أعتقد أن ذلك سيكون متاحًا حتى C # 7 / إصدار إطار العمل التالي

أعتقد أنه يمكنك استخدامه مع C # 2 ، ولكن ليس مع الدعم المدمج.

أو حتى معرفة ما إذا كان سيكون ذلك صاحب الأداء (يمكن أن تضيف استدعاءات الخاصية / الطريقة هذه إلى EqualityComparer)

ما الذي سيفعله هذا الفصل بشكل مختلف؟ إذا كان استدعاء obj == null ? 0 : obj.GetHashCode() أسرع من ذلك ، فيجب نقله إلى ValueTuple .

كنت أميل إلى إجراء 1+ لهذا الاقتراح منذ أسبوعين ، لكني أقل ميلًا في ضوء ValueTuple لتقليل النفقات العامة للتخصيص لخدعة استخدام Tuple لهذا الغرض ، يبدو أن هذا يقع بين كرسيين بالنسبة لي: إذا لم تكن بحاجة إلى شيء متخصص بشكل خاص ، فيمكنك استخدام ValueTuple ، ولكن إذا كنت بحاجة إلى شيء ما بعد ذلك ، فإن فصلًا كهذا لن يذهب بعيدًا كاف.

وعندما يكون لدينا C # 7 ، سيكون لدينا السكر النحوي لجعله أسهل.

تضمين التغريدة

ما الذي سيفعله هذا الفصل بشكل مختلف؟ إذا كان استدعاء obj == فارغًا؟ 0: obj.GetHashCode () هو أي أسرع ، مما ينبغي نقله إلى ValueTuple.

لماذا لا تستخدم ValueTuple فقط فئة Hash للحصول على أكواد التجزئة؟ سيؤدي ذلك أيضًا إلى تقليل LOC في الملف بشكل كبير (والذي يبلغ حاليًا حوالي 2000 سطر).

تعديل:

إذا لم تكن بحاجة إلى شيء متخصص بشكل خاص ، فيمكنك استخدام ValueTuple

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

يمكنني بالفعل أن أتخلف عن الركب.

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

على سبيل المثال ، كان علينا أن نبرمج xxHash32 و xxHash64 و Metro128 وكذلك اختزال الحجم من 128 إلى 64 ومن 64 إلى 32 بت بأنفسنا. يمكن أن يساعد وجود مجموعة من الوظائف المحسّنة المطورين على تجنب كتابة وظائفهم غير المحسّنة و / أو التي تجرها الدواب (أعلم ، لقد وجدنا بعض الأخطاء في منطقتنا أيضًا) ؛ ولكن لا يزال بإمكانك الاختيار من بينها حسب الاحتياجات.

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

redknightlois يسعدني إضافة تطبيق SpookyHash الخاص بي إلى جهد من هذا القبيل.

svick Careful with string.GetHashCode () ، هذا محدد جدًا ، ولسبب وجيه للغاية ، هجمات Hash DoS.

terrajobst ، ما مدى هذا في قائمة انتظار / مراجعة واجهة برمجة التطبيقات؟ أعتقد أنها واجهة برمجة تطبيقات بسيطة أردنا دائمًا إضافتها إلى النظام الأساسي وربما لدينا الآن كتلة حرجة كافية للقيام بذلك بالفعل؟

cc:ellismg

أعتقد أنه جاهز للمراجعة في حالته الحالية.

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

jamesqo يجب أن يكون هناك أيضًا أساس long .

redknightlois ، تبدو معقولة. لقد قمت بتحديث الاقتراح ليشمل long حمولات زائدة قدرها Combine .

هل اقتراحJonHanna ليس جيدًا بما فيه الكفاية؟

C# return ValueTuple.Create(a, b, c).GetHashCode();

ما لم تكن هناك أسباب كافية تجعل هذا غير جيد بما فيه الكفاية ، لا نعتقد أنه يتم إجراء الخفض.

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

واسمحوا لي أن أبين مع مثال على ذلك.

لنفترض أننا أخذنا الكود الفعلي الذي يتم استخدامه لـ ValueTuple ونستخدم الثوابت لاستدعائه.

        internal static class HashHelpers
        {
            public static int Combine(int h1, int h2)
            {
                // The jit optimizes this to use the ROL instruction on x86
                // Related GitHub pull request: dotnet/coreclr#1830
                uint shift5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
                return ((int)shift5 + h1) ^ h2;
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static int TryStaticCall()
        {
            return HashHelpers.Combine(10202, 2003);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static int TryValueTuple()
        {
            return ValueTuple.Create(10202, 2003).GetHashCode();
        }
    }

الآن في ظل وجود مترجم محسن هناك احتمالات أنه لا ينبغي أن يكون هناك أي فرق ، ولكن في الواقع هناك.

هذا هو الرمز الفعلي لـ ValueTuple

image
إذن الآن ما الذي يمكن رؤيته هنا؟ أولاً نقوم بإنشاء بنية في المكدس ، ثم نقوم باستدعاء كود التجزئة الفعلي.

قارنها الآن باستخدام HashHelper.Combine والذي يمكن أن يكون التنفيذ الفعلي لجميع الأغراض Hash.Combine

image

أنا أعرف!!!
لكن دعنا لا نتوقف عند هذا الحد ... لنستخدم المعلمات الفعلية:

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static int TryStaticCall(int h1, int h2)
        {
            return HashHelpers.Combine(h1, h2);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static int TryValueTuple(int h1, int h2)
        {
            return ValueTuple.Create(h1, h2).GetHashCode();
        }

        static unsafe void Main(string[] args)
        {
            var g = new Random();
            int h1 = g.Next();
            int h2 = g.Next(); 
            Console.WriteLine(TryStaticCall(h1, h2));
            Console.WriteLine(TryValueTuple(h1, h2));
        }

image

الشيء الجيد ، هذا مستقر للغاية. لكن دعنا نقارن ذلك بالبديل:

image

الآن دعنا نذهب في البحر ...

        internal static class HashHelpers
        {
            public static int Combine(int h1, int h2)
            {
                // The jit optimizes this to use the ROL instruction on x86
                // Related GitHub pull request: dotnet/coreclr#1830
                uint shift5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
                return ((int)shift5 + h1) ^ h2;
            }
            public static int Combine(int h1, int h2, int h3, int h4)
            {
                return Combine(Combine(h1, h2), Combine(h3, h4));
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static int TryStaticCall(int h1, int h2, int h3, int h4)
        {
            return HashHelpers.Combine(h1, h2, h3, h4);
        }

والنتيجة واضحة جدا

image

لا يمكنني حقًا فحص الكود الفعلي الذي ينشئه JIT للمكالمة ، ولكن مجرد prolog و epilog يكفيان لتبرير إدراج الاقتراح.

image

خلاصة التحليل بسيطة: أن نوع الحجز هو struct لا يعني أنه مجاني :)

تم طرح الأداء خلال الاجتماع. السؤال هو ما إذا كان من المحتمل أن تكون واجهة برمجة التطبيقات هذه على المسار السريع. لأكون واضحًا ، أنا لا أقول أنه لا ينبغي أن يكون لدينا API. أنا أقول فقط ما لم يكن هناك سيناريو ملموس من الصعب تصميم واجهة برمجة التطبيقات لأننا لا نستطيع أن نقول "نحتاجها لـ X ، وبالتالي فإن مقياس النجاح هو ما إذا كان بإمكان X استخدامها". هذا مهم لواجهات برمجة التطبيقات التي لا تسمح لك بفعل شيء جديد ، بل تفعل الشيء نفسه بطريقة محسّنة أكثر.

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

terrajobst ، كان الأداء الدافع الرئيسي لهذا الاقتراح ولكن ليس الدافع الوحيد. سيساعد وجود نوع مخصص في قابلية الاكتشاف ؛ حتى مع دعم tuple المدمج في C # 7 ، قد لا يعرف المطورون بالضرورة أنهم متساوون من حيث القيمة. حتى لو فعلوا ذلك ، فقد ينسون أن tuples يتجاوز GetHashCode ، ومن المحتمل أن ينتهي بهم الأمر إلى Google في كيفية تنفيذ GetHashCode في .NET.

أيضًا ، هناك مشكلة تصحيح دقيقة في استخدام ValueTuple.Create.GetHashCode . العناصر الثمانية الماضية ، تم تجزئة العناصر الثمانية الأخيرة فقط ؛ يتم تجاهل الباقي.

terrajobst في RavenDB ، حقق أداء https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/InternalUtilities/Hash.cs تحقق أيضًا من المناقشة حول Roslyn على وجه التحديد هنا: https: // github .com / dotnet / coreclr / issues / 1619 ... لذلك عندما يكون الأداء هو المفتاح ، لا يمكننا استخدام المنصة المقدمة وعلينا أن نحدد المنصة الخاصة بنا (ودفع العواقب).

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

تضمين التغريدة

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

إذن أنت تقول أن إضافة فصل مساعد سيكون أمرًا سيئًا ، لأنه سيشجع الناس على إلقاء وظيفة المساعد دون التفكير في كيفية عمل تجزئة مناسبة؟

يبدو أن العكس هو الصحيح في الواقع ؛ Hash.Combine بشكل عام إلى تحسين تطبيقات GetHashCode . يمكن للأشخاص الذين يعرفون كيفية إجراء التجزئة تقييم Hash.Combine لمعرفة ما إذا كان يناسب حالة الاستخدام الخاصة بهم. سيستخدم المبتدئون الذين لا يعرفون شيئًا عن التجزئة حقًا Hash.Combine بدلاً من xor-ing فقط (أو الأسوأ من ذلك ، إضافة) الحقول المكونة لأنهم لا يعرفون كيفية إجراء التجزئة المناسبة.

لقد ناقشنا هذا أكثر قليلاً وأقنعتنا :-)

بعض الأسئلة الأخرى:

  1. نحن بحاجة إلى تحديد مكان وضع هذا النوع. يبدو إدخال مساحة اسم جديدة أمرًا غريبًا ؛ System.Numerics بالرغم من ذلك. System.Collections.Generic أيضًا ، لأنه يحتوي على مقارنات وغالبًا ما يتم استخدام التجزئة في سياق المجموعات.
  2. هل يجب أن نقدم نمط منشئ خالٍ من التخصيص لدمج عدد غير معروف من أكواد التجزئة؟

في (2) ، قال Eilon :

كمرجع ، يستخدم ASP.NET Core (وسابقاته والمشاريع ذات الصلة) HashCodeCombiner: https://github.com/aspnet/Common/blob/dev/src/Microsoft.Extensions.HashCodeCombiner.Sources/HashCodeCombiner.cs

(ذكرها @ David Fowler في موضوع GitHub منذ عدة أشهر.)

وهذا مثال على الاستخدام: https://github.com/aspnet/Mvc/blob/760c8f38678118734399c58c2dac981ea6e47046/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs#L129 -L144

ج #
var hashCodeCombiner = HashCodeCombiner.Start () ،
hashCodeCombiner.Add (IsMainPage؟ 1: 0) ،
hashCodeCombiner.Add (ViewName، StringComparer.Ordinal) ؛
hashCodeCombiner.Add (ControllerName، StringComparer.Ordinal) ؛
hashCodeCombiner.Add (AreaName، StringComparer.Ordinal) ؛

إذا (ViewLocationExpanderValues! = خالية)
{
foreach (عنصر var في ViewLocationExpanderValues)
{
hashCodeCombiner.Add (item.Key، StringComparer.Ordinal) ؛
hashCodeCombiner.Add (item.Value، StringComparer.Ordinal) ؛
}
}

إرجاع hashCodeCombiner ؛
""

لقد ناقشنا هذا أكثر قليلاً وأقنعتنا :-)

🎉

يبدو إدخال مساحة اسم جديدة أمرًا غريبًا ؛ System.Numerics قد تعمل بالرغم من ذلك.

إذا قررنا عدم إضافة مساحة اسم جديدة ، فيجب ملاحظة أن أي كود به فئة تسمى Hash وتوجيه using System.Numerics سوف يفشل في التحويل البرمجي مع وجود خطأ غامض في النوع.

هل يجب أن نقدم نمط منشئ خالٍ من التخصيص لدمج عدد غير معروف من أكواد التجزئة؟

هذه فكرة رائعة. كاقتراحين أوليين ، ربما يجب علينا تسميتها HashBuilder (a la StringBuilder ) وجعلها return this بعد كل Add طريقة لتسهيل الأمر لإضافة تجزئات ، مثل:

public override int GetHashCode()
{
    return HashBuilder.Create(_field1)
        .Add(_field2)
        .Add(_field3)
        .ToHash();
}

jamesqo يرجى تحديث الاقتراح في الأعلى عندما يكون هناك إجماع على الموضوع. يمكننا بعد ذلك إجراء المراجعة النهائية. تعيين لك الآن وأنت تقود التصميم ؛-)

إذا قررنا عدم إضافة مساحة اسم جديدة ، فيجب ملاحظة أن أي كود به فئة تسمى Hash وتوجيه using System.Numerics سوف يفشل في التحويل البرمجي مع وجود خطأ غامض في النوع.

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

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

كاقتراحين أوليين ، ربما ينبغي علينا تسميته HashBuilder

كأول تقدير تقريبي ، كنت أفكر في الجمع بين الإحصائيات والمنشئ في نوع واحد ، مثل:

ج #
System.Collections.Generic. مساحة الاسم
{
HashCode الهيكل العام
{
الجمع العام الثابت العام (int hash1، int hash2) ؛
الجمع العام الثابت العام (int hash1، int hash2، int hash3) ؛
الجمع العام الثابت العام (int hash1، int hash2، int hash3، int hash4) ؛
الجمع العام الثابت العام (int hash1، int hash2، int hash3، int hash4، int hash5) ؛
الجمع العام الثابت العام (int hash1، int hash2، int hash3، int hash4، int hash5، int hash6) ؛

    public static long Combine(long hash1, long hash2);
    public static long Combine(long hash1, long hash2, long hash3);
    public static long Combine(long hash1, long hash2, long hash3, long hash4);
    public static long Combine(long hash1, long hash2, long hash3, long hash4, long hash5);
    public static long Combine(long hash1, long hash2, long hash3, long hash4, long hash5, longhash6);

    public static int CombineHashCodes<T1, T2>(T1 o1, T2 o2);
    public static int CombineHashCodes<T1, T2, T3>(T1 o1, T2 o2, T3 o3);
    public static int CombineHashCodes<T1, T2, T3, T4>(T1 o1, T2 o2, T3 o3, T4 o4);
    public static int CombineHashCodes<T1, T2, T3, T4, T5>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5);
    public static int CombineHashCodes<T1, T2, T3, T4, T5, T6>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5, T6 o6);

    public void Combine(int hashCode);
    public void Combine(long hashCode);
    public void Combine<T>(T obj);
    public void Combine(string text, StringComparison comparison);

    public int Value { get; }
}

}

This allows for code like this:

``` C#
return HashCode.Combine(value1, value2);

إلى جانب:

ج #
var hashCode = new HashCode () ،
hashCode.Combine (IsMainPage؟ 1: 0) ،
hashCode.Combine (ViewName، StringComparer.Ordinal) ؛
hashCode.Combine (ControllerName، StringComparer.Ordinal) ؛
hashCode.Combine (AreaName، StringComparer.Ordinal) ؛

إذا (ViewLocationExpanderValues! = خالية)
{
foreach (عنصر var في ViewLocationExpanderValues)
{
hashCode.Combine (item.Key، StringComparer.Ordinal) ؛
hashCode.Combine (item.Value، StringComparer.Ordinal) ؛
}
}

إرجاع hashCode.Value ؛
""

أفكار؟

تعجبني فكرة this من طرق المثيل Combine ).

أود حتى أن أذهب إلى أبعد من إزالة الطرق الثابتة تمامًا واحتفظ فقط بأساليب المثيل ...

Combine(long hashCode) إلى int . هل نريد ذلك حقًا؟
ما هي حالة استخدام الأحمال الزائدة long في المقام الأول؟

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

استخدمنا فئة ثابتة Hashing لتجنب تضارب الأسماء ويبدو الرمز جيدًا.

redknightlois أتساءل عما إذا كان يجب أن نتوقع نفس الكود "السيئ" أيضًا في حالة البنية غير العامة مع حقل int واحد.
إذا كان هذا لا يزال رمز تجميع "سيئًا" ، أتساءل عما إذا كان بإمكاننا تحسين JIT للقيام بعمل أفضل في التحسينات هنا. يجب أن تكون إضافة واجهات برمجة التطبيقات لحفظ بعض التعليمات هي الملاذ الأخير IMO.

redknightlois Curious ، هل يُنشئ JIT أي رمز أسوأ إذا كان HashCode ) احتواء السجل؟ سيكون فقط int كبير.

أيضًا ، لقد رأيت الكثير من طلبات السحب في coreclr مؤخرًا لتحسين الكود الذي تم إنشاؤه حول الهياكل ، ويبدو أن dotnet / coreclr # 8057 سيمكن هذه التحسينات. ربما الرمز الذي يولده JIT سيكون أفضل بعد هذا التغيير؟

تحرير: أرى أن karelz قد ذكر بالفعل

Karelz ، أنا أتفق int -size Struct (وهو ما أعتقد أنه كذلك ، لا يحتوي ImmutableArray على أي نفقات إضافية على سبيل المثال) ، فإن الحمل الزائد الثابت هو زائدة عن الحاجة ويمكن إزالتها.

terrajobst لدي بعض الأفكار الأخرى:

  • أعتقد أنه يمكننا الجمع بين أفكارك وأفكاري قليلاً. HashCode يبدو اسمًا جيدًا ؛ لا يجب أن تكون بنية قابلة للتغيير تتبع نمط الباني. بدلاً من ذلك ، يمكن أن يكون غلافًا غير قابل للتغيير حول int ، وكل عملية Combine يمكن أن ترجع قيمة HashCode . فمثلا
public struct HashCode
{
    private readonly int _hash;

    public HashCode Combine(int hash) => return new HashCode(CombineCore(_hash, hash));

    public HashCode Combine<T>(T item) => Combine(EqualityComparer<T>.Default.GetHashCode(item));
}

// Usage
HashCode combined = new HashCode(_field1)
    .Combine(_field2)
    .Combine(_field3);
  • يجب أن يكون لدينا عامل تشغيل ضمني للتحويل إلى int حتى لا يضطر الأشخاص إلى الحصول على آخر مكالمة .Value .
  • Re Combine هل هذا هو الاسم الأفضل؟ يبدو أكثر وصفيًا ، لكن Add أقصر وأسهل في الكتابة. ( Mix هو بديل آخر ، لكن الكتابة مؤلمة قليلاً.)

    • public void Combine(string text, StringComparison comparison) : لا أعتقد أن هذا ينتمي حقًا إلى نفس النوع ، لأن هذا لا علاقة له بالسلاسل. علاوة على ذلك ، من السهل كتابة StringComparer.XXX.GetHashCode(str) لتلك الأوقات النادرة التي تحتاج إلى القيام بذلك.

    • يجب أن نزيل الأحمال الزائدة الطويلة من هذا النوع وأن يكون لدينا نوع HashCode منفصل لفترات طويلة. شيء مثل Int64HashCode أو LongHashCode .

لقد قمت بتنفيذ عينة صغيرة من الأشياء على TryRoslyn: http://tinyurl.com/zej9yux

لحسن الحظ من السهل التحقق. والخبر السار هو أنه يعمل بشكل صحيح كما هو 👍

image

يجب أن يكون لدينا عامل تشغيل ضمني للتحويل إلى int حتى لا يضطر الأشخاص إلى الحصول على آخر مكالمة .Value.

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

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static int TryHashCombiner(int h1, int h2, int h3, int h4)
        {
            var h = new HashCode(h1).Combine(h2).Combine(h3).Combine(h4);
            return h.Value;
        }

Re Combine هل هذا هو أفضل اسم؟ يبدو الأمر أكثر وصفيًا ، لكن الإضافة أقصر وأسهل في الكتابة. (المزيج هو بديل آخر ، لكن الكتابة مؤلمة قليلاً).

الجمع هو الاسم الفعلي المستخدم في مجتمع التجزئة afaik. ويعطيك نوعًا ما فكرة واضحة عما يفعله.

jamesqo هناك الكثير من وظائف التجزئة ، كان علينا تنفيذ إصدارات سريعة جدًا من 32 بت و 64 بت إلى 128 بت لـ RavenDB (ونستخدم كل واحدة لأغراض مختلفة).

يمكننا التفكير في هذا التصميم ببعض الآليات القابلة للتوسيع مثل هذا:

        internal interface IHashCode<T> where T : struct
        {
            T Combine(T h1, T h2);
        }

        internal struct RotateHashCode : IHashCode<int>, IHashCode<long>
        {
            long IHashCode<long>.Combine(long h1, long h2)
            {
                // The jit optimizes this to use the ROL instruction on x86
                // Related GitHub pull request: dotnet/coreclr#1830
                ulong shift5 = ((ulong)h1 << 5) | ((ulong)h1 >> 27);
                return ((int)shift5 + h1) ^ h2;
            }

            int IHashCode<int>.Combine(int h1, int h2)
            {
                // The jit optimizes this to use the ROL instruction on x86
                // Related GitHub pull request: dotnet/coreclr#1830
                uint shift5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
                return ((int)shift5 + h1) ^ h2;
            }
        }

        internal struct HashCodeCombiner<T, W> where T : struct, IHashCode<W>
                                               where W : struct
        {
            private static T hasher;
            public W Value;

            static HashCodeCombiner()
            {
                hasher = new T();
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public HashCodeCombiner(W seed)
            {
                this.Value = seed;
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public HashCodeCombiner<T,W> Combine( W h1 )
            {
                Value = hasher.Combine(this.Value, h1);
                return this;
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static int TryHashCombinerT(int h1, int h2, int h3, int h4)
        {
            var h = new HashCodeCombiner<RotateHashCode, int>(h1).Combine(h2).Combine(h3).Combine(h4);
            return h.Value;
        }

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

تحرير: التفكير بصوت عالٍ هنا في آلية عامة لدمج وظائف تجزئة التشفير وغير المشفرة في مظلة مفهوم التجزئة الواحدة.

تضمين التغريدة

لا يجب أن تكون بنية قابلة للتغيير تتبع نمط الباني

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

أعتقد أنه يمكننا الجمع بين أفكارك وأفكاري قليلاً.

👍 فماذا عن:

ج #
HashCode الهيكل العام
{
إنشاء HashCode العام الثابت(T obj) ؛

[Pure] public HashCode Combine(int hashCode);
[Pure] public HashCode Combine(long hashCode);
[Pure] public HashCode Combine<T>(T obj);
[Pure] public HashCode Combine(string text, StringComparison comparison);

public int Value { get; }

public static implicit operator int(HashCode hashCode);

}

This allows for code like this:

``` C#
public override int GetHashCode()
{
    return HashCode.Create(value1).Combine(value2);
}

أضافة إلى هذا:

ج #
var hashCode = new HashCode ()
الجمع (IsMainPage؟ 1: 0)
. الجمع بين (ViewName، StringComparer.Ordinal)
.Combine (ControllerName ، StringComparer.Ordinal)
.Combine (AreaName، StringComparer.Ordinal) ؛

إذا (ViewLocationExpanderValues! = خالية)
{
foreach (عنصر var في ViewLocationExpanderValues)
{
hashCode = hashCode.Combine (item.Key، StringComparer.Ordinal) ؛
hashCode = hashCode.Combine (item.Value، StringComparer.Ordinal) ؛
}
}

إرجاع hashCode.Value ؛
""

terrajobst أفكار:

  1. يجب إزالة طريقة المصنع Create<T> . بخلاف ذلك ، ستكون هناك طريقتان لكتابة الشيء نفسه ، HashCode.Create(_val) أو new HashCode().Combine(_val) . أيضًا ، وجود أسماء مختلفة لـ Create / Combine لن يكون مناسبًا للفروق لأنه إذا أضفت حقلًا أول جديدًا ، فسيتعين عليك تغيير سطرين.
  2. لا أعتقد أن الحمل الزائد قبول سلسلة / StringComparison ينتمي هنا ؛ HashCode ليس له علاقة بالسلاسل. بدلاً من ذلك ، ربما يجب أن نضيف واجهة برمجة تطبيقات GetHashCode(StringComparison) إلى السلسلة؟ (أيضًا كل هذه المقارنات ترتيبية ، وهو السلوك الافتراضي string.GetHashCode .)
  3. ما الهدف من وجود Value ، إذا كان هناك بالفعل عامل ضمني للتحويل إلى int ؟ مرة أخرى ، سيؤدي هذا إلى قيام أشخاص مختلفين بكتابة أشياء مختلفة.
  4. علينا أن ننقل الحمولة الزائدة long إلى نوع جديد. HashCode سيكون عرضه 32 بت فقط ؛ لا يمكن أن يصلح لفترة طويلة.
  5. دعنا نضيف بعض الأحمال الزائدة باستخدام أنواع غير موقعة ، لأنها أكثر شيوعًا في التجزئة.

ها هي واجهة برمجة التطبيقات (API) المقترحة الخاصة بي:

public struct HashCode
{
    public HashCode Combine(int hash);
    public HashCode Combine(uint hash);
    public HashCode Combine<T>(T obj);

    public static implicit operator int(HashCode hashCode);
    public static implicit operator uint(HashCode hashCode);
}

public struct Int64HashCode
{
    public Int64HashCode Combine(long hash);
    public Int64HashCode Combine(ulong hash);

    public static implicit operator long(Int64HashCode hashCode);
    public static implicit operator ulong(Int64HashCode hashCode);
}

باستخدام هذه الأساليب فقط ، لا يزال من الممكن كتابة المثال من ASP.NET كـ

var hashCode = new HashCode()
    .Combine(IsMainPage ? 1 : 0)
    .Combine(ViewName)
    .Combine(ControllerName)
    .Combine(AreaName);

if (ViewLocationExpanderValues != null)
{
    foreach (var item in ViewLocationExpanderValues)
    {
        hashCode = hashCode.Combine(item.Key);
        hashCode = hashCode.Combine(item.Value);
    }
}

return hashCode;

تضمين التغريدة

ما الهدف من وجود Value ، إذا كان هناك بالفعل عامل ضمني للتحويل إلى int ؟ مرة أخرى ، سيؤدي هذا إلى قيام أشخاص مختلفين بكتابة أشياء مختلفة.

تنص إرشادات تصميم الإطار لأحمال المشغل الزائدة على ما يلي:

يوفر CONSIDER طرقًا بأسماء مألوفة تتوافق مع كل عامل محمّل فوق طاقته.

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

على وجه التحديد ، تعد F # إحدى اللغات التي تجعل استدعاء عوامل التحويل الضمنية أمرًا محرجًا.


أيضًا ، لا أعتقد أن وجود طريقة واحدة فقط للقيام بالأشياء هو بهذه الأهمية. في رأيي ، من المهم جعل واجهة برمجة التطبيقات ملائمة. إذا أردت فقط دمج أكواد التجزئة ذات القيم القليلة ، أعتقد أن HashCode.CombineHashCodes(value1, value2, value3) أبسط وأقصر وأسهل للفهم من new HashCode().Combine(value1).Combine(value2).Combine(value3) .

لا تزال واجهة برمجة تطبيقات طريقة المثيل مفيدة للحالات الأكثر تعقيدًا ، لكنني أعتقد أن الحالة الأكثر شيوعًا يجب أن تحتوي على واجهة برمجة تطبيقات الطريقة الثابتة الأبسط.

svick ، وجهة نظرك حول اللغات الأخرى التي لا تدعم المشغلين تعتبر شرعية جيدًا. أعطي ، دعونا نضيف Value إذن.

لا أعتقد أن وجود طريقة واحدة فقط للقيام بالأشياء هو بهذه الأهمية.

انه مهم. إذا قام شخص ما بذلك بطريقة ما ، وقرأ رمز الشخص الذي يقوم بذلك بطريقة أخرى ، فسيتعين عليه / عليها أن يقوم بذلك في Google بالطريقة الأخرى.

إذا أردت فقط دمج رموز التجزئة ذات القيم القليلة ، أعتقد أن HashCode.CombineHashCodes (القيمة 1 ، القيمة 2 ، القيمة 3) هي أبسط وأقصر وأسهل للفهم من HashCode () الجديد. اجمع (القيمة 1). اجمع (القيمة 2). القيمة 3).

  • تكمن مشكلة الطريقة الثابتة في أنه ، نظرًا لعدم وجود حمل زائد لقيمة params int[] ، فسنضطر إلى إضافة حمولات زائدة لكل منطقة مختلفة ، وهو أقل بكثير مقابل المال. من الأجمل أن يكون لديك طريقة واحدة تغطي جميع حالات الاستخدام.
  • سيكون من السهل فهم النموذج الثاني بمجرد رؤيته مرة أو مرتين. في الواقع ، يمكنك القول إنه أكثر قابلية للقراءة ، لأنه من الأسهل ربطه عموديًا (وبالتالي يقلل الاختلافات عند إضافة / إزالة حقل):
public override int GetHashCode()
{
    return new HashCode()
        .Combine(_field1)
        .Combine(_field2)
        .Combine(_field3)
        .Combine(_field4);
}

[svick] لا أعتقد أن وجود طريقة واحدة فقط للقيام بالأشياء بهذه الأهمية.

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

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

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

[svick] إذا أردت فقط دمج رموز التجزئة ذات القيم القليلة ، أعتقد أن HashCode.CombineHashCodes (القيمة 1 ، القيمة 2 ، القيمة 3) هي أبسط وأقصر وأسهل في الفهم من HashCode () الجديد. اجمع (القيمة 1). اجمع (القيمة 2) اجمع (القيمة 3).

أتفق مع jamesqo : ما يعجبني في نمط البناء الذي

[jamesqo] لا أعتقد أن الحمل الزائد قبول سلسلة / StringComparison ينتمي هنا ؛ HashCode ليس له علاقة بالسلاسل

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

[jamesqo] علينا نقل الحمل الزائد الطويل إلى نوع جديد. HashCode سيكون عرضه 32 بت فقط ؛ لا يمكن أن يصلح لفترة طويلة.

هذه نقطة جيدة. هل نحتاج حتى إلى إصدار long على الإطلاق؟ لقد تركته فقط لأنه مذكور أعلاه ولم أفكر فيه حقًا.

الآن بعد أن أصبحت كذلك ، يبدو أننا يجب أن نترك 32 بت فقط لأن هذا ما يدور حوله .NET GetHashCode() . في هذا السياق ، لست متأكدًا من أننا يجب أن نضيف الإصدار uint . إذا كنت تستخدم التجزئة خارج هذا المجال ، فأعتقد أنه من المقبول توجيه الأشخاص إلى خوارزميات التجزئة ذات الأغراض العامة التي لدينا في System.Security.Cryptography .

ج #
HashCode الهيكل العام
{
إنشاء HashCode العام الثابت(T obj) ؛

[Pure] public HashCode Combine(int hashCode);
[Pure] public HashCode Combine<T>(T obj);

public int Value { get; }

public static implicit operator int(HashCode hashCode);

}
""

الآن بعد أن أصبحت كذلك ، يبدو أننا يجب أن نترك 32 بت فقط لأن هذا ما يدور حوله .NET GetHashCode (). في هذا السياق ، لست متأكدًا من أننا يجب أن نضيف نسخة uint. إذا كنت تستخدم التجزئة خارج هذا المجال ، أعتقد أنه من المقبول توجيه الأشخاص إلى خوارزميات التجزئة ذات الأغراض العامة الأكثر لدينا في System.Security.Cryptography.

terrajobst هناك أنواع مختلفة جدًا من خوارزميات التجزئة ، وهي حديقة حيوانات حقيقية. في الواقع ، ربما 70٪ غير مشفرة حسب التصميم. وربما تم تصميم أكثر من نصف هؤلاء للتعامل مع 64+ بت (الهدف المشترك هو 128/256). قرر إطار العمل استخدام 32 بتًا (لم أكن هناك) لأنه في ذلك الوقت كان x86 لا يزال مستهلكًا ضخمًا ويتم استخدام التجزئة في كل مكان ، لذلك كان الأداء على الأجهزة الأقل أهمية قصوى.

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

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

تكون المشكلات الناتجة عن التبسيط المفرط أحيانًا أسوأ من مشكلات التصميم التي يتم تقديمها عند إجبارها على التعامل مع أشياء معقدة. تبسيط الوظائف إلى هذه الدرجة الكبيرة لأن المطورين يُنظر إليهم على أنهم not being able to deal with having multiple options يمكن أن يؤدي بسهولة إلى مسار الحالة الحالية لـ SIMD. لا يستطيع معظم المطورين المهتمين بالأداء استخدامه ، ولن يستخدمه أي شخص آخر إما لأن معظمهم لا يتعامل مع تطبيقات حساسة للأداء لها أهداف إنتاجية جيدة على أي حال.

حالة التجزئة متشابهة ، المجالات التي ستستخدم فيها 32 بت مقيدة للغاية (معظمها مغطى بالفعل بواسطة إطار العمل نفسه) ، أما البقية فأنت غير محظوظ.

image

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

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

هذا هو 2 سنتي في المناقشة.

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

ما لم تكن تعتقد أنه لا يزال من المفيد القيام بدمج التجزئة في 128 بت ثم التحويل إلى int بحيث يمكن إرجاع النتيجة من GetHahsCode.

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

redknightlois ، أود أن أقترح ما يلي:

تضمين التغريدة

ارتكاب الأخطاء هنا سيكون له تأثير.

أعتقد أنه إذا أردنا في المستقبل دعم السيناريوهات الأكثر تقدمًا ، فيمكننا فعل ذلك في نوع منفصل من HashCode . لا ينبغي للقرارات هنا أن تؤثر حقًا على تلك الحالات.

لقد خلقت مشكلة مختلفة لبدء معالجتها.

redknightlois : +1 :. راجع للشغل ، لقد أجبت قبل أن أتمكن من تعديل تعليقي ، لكنني في الواقع جربت فكرتك (أعلاه) للحصول على عمل التجزئة مع أي نوع (int ، long ، decimal ، إلخ) وتكوين منطق التجزئة الأساسي في بنية: https://github.com/jamesqo/HashApi (كان استخدام العينة هنا ). ولكن ، انتهى وجود معلمتين عامتين إلى أن تكون معقدة للغاية ، وانتهى الأمر باستدلال نوع المترجم إلى عدم العمل عندما حاولت استخدام واجهة برمجة التطبيقات. لذا ، نعم ، إنها فكرة جيدة إجراء تجزئة أكثر تقدمًا في مشكلة منفصلة في الوقت الحالي.

terrajobst تبدو واجهة برمجة التطبيقات جاهزة تقريبًا ، ولكن هناك تغييرهما .

  • لم أكن أرغب في البداية في طريقة المصنع الثابت ، نظرًا لأن HashCode.Create(x) له نفس تأثير new HashCode().Combine(x) . لكنني غيرت رأيي بشأن ذلك لأن هذا يعني تجزئة واحدة إضافية. بدلاً من ذلك ، لماذا لا نعيد تسمية Create إلى Combine ؟ يبدو أنه من المزعج نوعًا ما أن تضطر إلى كتابة شيء واحد للحقل الأول وآخر للحقل الثاني.
  • أعتقد أنه يجب أن يكون لدينا HashCode لتطبيق IEquatable<HashCode> وتنفيذ بعض عوامل تشغيل المساواة. لا تتردد في إخباري إذا كان لديك اعتراض.

(نأمل) الاقتراح النهائي:

public struct HashCode : IEquatable<HashCode>
{
    public static HashCode Combine(int hash);
    public static HashCode Combine<T>(T obj);

    public HashCode Combine(int hash);
    public HashCode Combine<T>(T obj);

    public int Value { get; }

    public static implicit operator int(HashCode hashCode);

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

    public override bool Equals(object obj);
    public override bool Equals(HashCode other);
    public override int GetHashCode();
}

// Usage:

public override int GetHashCode()
{
    return HashCode
        .Combine(_field1)
        .Combine(_field2)
        .Combine(_field3)
        .Combine(_field4);
}

terrajobst قال:

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

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

بدون واجهة برمجة التطبيقات هذه ، ستحتاج إلى القيام بشيء غريب مثل:

HashCode.Combine(str1.ToLowerInvariant()).Combine(str2.ToLowerInvariant())

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

Eilon في مثل هذه الحالة أتوقع أن الرمز يجب أن يستدعي صراحة string.GetHashCode(StringComparison comparison) وهو ثقافة / مدرك للحالة وتمرير النتيجة كـ int إلى Combine .

c# HashCode.Combine(str1.GetHashCode(StringComparer.Ordinal)).Combine(...)

Eilon ، يمكنك فقط استخدام StringComparer.InvariantCultureIgnoreCase.GetHashCode.

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

عادل بما فيه الكفاية ، بدمج كل ما قيل أعلاه ، ماذا عن هذا الشكل إذن:

ج #
System.Collections.Generic. مساحة الاسم
{
HashCode الهيكل العام: IEquatable
{
HashCode Combine العامة الثابتة (int التجزئة) ؛
دمج HashCode العام الثابت(T obj) ؛
HashCode Combine العامة الثابتة (نص سلسلة ، مقارنة StringComparison) ؛

    public HashCode Combine(int hash);
    public HashCode Combine<T>(T obj);
    public HashCode Combine(string text, StringComparison comparison);

    public int Value { get; }

    public static implicit operator int(HashCode hashCode);

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

    public override bool Equals(object obj);
    public override bool Equals(HashCode other);
    public override int GetHashCode();
}

}

// الاستخدام:

تجاوز عام int GetHashCode ()
{
إرجاع HashCode.Combine (_field1)
.Combine (_field2)
.Combine (_field3)
.Combine (_field4) ؛
}
""

شحنه! :-)

terrajobst _Hold on --_ ألا يمكن تطبيق Combine(string, StringComparison) كطريقة امتداد؟

public static class HashCodeExtensions
{
    public static HashCode Combine(this HashCode hashCode, string text, StringComparison comparison)
    {
        switch (comparison)
        {
            case StringComparison.Ordinal:
                return HashCode.Combine(StringComparer.Ordinal.GetHashCode(text));
            case StringComparison.OrdinalIgnoreCase:
                ...
        }
    }
}

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

( تحرير: أيضًا System.Numerics من المحتمل أن يكون مساحة اسم أفضل ، ما لم يكن لدينا أنواع ذات صلة بالتبادل في المجموعات. عام اليوم الذي لست على علم به.)

LGTM. سأذهب التمديد.

نعم ، يمكن أن تكون طريقة امتداد ، ولكن ما المشكلة التي تحلها؟

تضمين التغريدة

نعم ، يمكن أن تكون طريقة امتداد ، ولكن ما المشكلة التي تحلها؟

كنت أقترح في رمز ASP.NET. إذا كان الأمر شائعًا بالنسبة لحالة الاستخدام الخاصة بهم ، فلا بأس بذلك ، ولكن قد لا يكون هذا صحيحًا بالنسبة للمكتبات / التطبيقات الأخرى. إذا اتضح أن هذا شائع فيما بعد ، فيمكننا دائمًا إعادة التقييم ونقرر إضافته في عرض منفصل.

Mhhh هذا هو جوهر على أي حال. بمجرد تحديده سيكون جزءًا من التوقيع على أي حال. ألغِ التعليق. لا بأس كما هو.

استخدام طرق الامتداد مفيد للحالات التي:

  1. إنه نوع موجود نود زيادته دون الحاجة إلى شحن تحديث للنوع نفسه
  2. حل مشاكل الطبقات
  3. فصل واجهات برمجة التطبيقات المشتركة الفائقة عن واجهات برمجة التطبيقات الأقل استخدامًا.

لا أعتقد أن (1) أو (2) تنطبق هنا. (3) سيساعد فقط إذا نقلنا الكود إلى تجميع مختلف عن HashCode أو إذا نقلناه إلى مساحة اسم مختلفة. كنت أزعم أن الأوتار شائعة بما فيه الكفاية بحيث لا تستحق ذلك. في الواقع ، كنت أزعم أنهم شائعون جدًا لدرجة أن معاملتهم على أنهم من الدرجة الأولى يكون منطقيًا أكثر من محاولة فصلهم بشكل مصطنع على نوع ملحق.

terrajobst ، لكي أكون واضحًا ، كنت أقترح التخلي عن واجهة برمجة التطبيقات API string تمامًا ، وترك الأمر لـ ASP.NET لكتابة طريقة التمديد الخاصة بهم للسلاسل.

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

نعم ، ولكن ما مدى شيوع رغبة شخص ما في الحصول على رمز التجزئة غير الترتيبي لسلسلة ، وهو السيناريو الوحيد الذي لا يهتم به التحميل الزائد الحالي Combine<T> ؟ (على سبيل المثال ، شخص يتصل بـ StringComparer.CurrentCulture.GetHashCode في تجاوزاتهم؟) قد أكون مخطئًا ، لكني لم أر الكثير.

آسف للتراجع عن هذا ؛ إنه بمجرد إضافة واجهة برمجة التطبيقات لن يكون هناك عودة.

نعم ، ولكن ما مدى شيوع أن يرغب شخص ما في الحصول على رمز التجزئة غير الترتيبي لسلسلة

قد أكون متحيزًا ، لكن ثبات الحالة شائع جدًا. بالتأكيد ، لا يهتم الكثير (إن وجد) برموز التجزئة الخاصة بالثقافة ولكن أكواد التجزئة التي تتجاهل الغلاف الذي يمكنني رؤيته تمامًا - وهذا يبدو ما يليهEilon (وهو StringComparison.OrdinalIgnoreCase ).

آسف للتراجع عن هذا ؛ إنه بمجرد إضافة واجهة برمجة التطبيقات لن يكون هناك عودة.

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

terrajobst حسنًا ، دعنا نضيفها: +1: العدد الأخير: لقد ذكرت هذا أعلاه ، ولكن هل يمكننا جعل مساحة الاسم Numerics بدلاً من المجموعات. إذا أردنا إضافة المزيد من الأنواع ذات الصلة بالتجزئة في المستقبل كما يقترح

انا احب هذا. 🍔

لا أعتقد أن Hashing تندرج من الناحية المفاهيمية في المجموعات. ماذا عن System.Runtime؟

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

karelz ، يمكن أن يعمل System.Runtime. سيكون نظام redknightlois مناسبًا ، نظرًا لأن هناك احتمالية لاستيراد مساحة الاسم هذه بالفعل. لا ، إذا كان هذا هو المناسب ، على الرغم من (مرة أخرى ، إذا تمت إضافة المزيد من أنواع التجزئة).

لا يجب أن نضعها في System.Runtime لأن هذا مخصص للحالات الباطنية والمتخصصة تمامًا. لقد تحدثت إلى KrzysztofCwalina وكلانا يعتقد أنه واحد من الاثنين:

  • System
  • System.Collections.*

كلانا يميل نحو System .

إذا كان ما نحتاجه هو سبب منطقي للذهاب إلى System يمكنني تجربة التبرير. لقد أنشأنا HashCode للمساعدة في تنفيذ object.GetHashCode() ، ويبدو أنه من المناسب أن يشترك كلاهما في مساحة الاسم.

terrajobst أعتقد أن System يجب أن يكون مساحة الاسم ، إذن. دعنا: shipit:

تم تحديث مواصفات API في الوصف.

[redknightlois] إذا كان ما نحتاج إليه هو سبب منطقي للذهاب إلى System يمكنني تجربة التبرير. لقد أنشأنا HashCode للمساعدة في تنفيذ object.GetHashCode() ، ويبدو أنه من المناسب أن يشترك كلاهما في مساحة الاسم.

كان هذا هو الأساس المنطقي الذي استخدمناه أنا و

تضمين التغريدة

أفترض أنك تريد تزويد العلاقات العامة بالتنفيذ أيضًا؟

terrajobst نعم ، بالتأكيد. شكرا لأخذ الوقت لمراجعة هذا!

نعم بالتأكيد.

حلو. في هذه الحالة سأتركها مخصصة لك. هذا جيد معك @ Karelz؟

شكرا لأخذ الوقت لمراجعة هذا!

نشكرك على الوقت الذي استغرقته في العمل معنا على شكل واجهة برمجة التطبيقات. يمكن أن يكون الانتقال ذهابًا وإيابًا عملية مؤلمة. نحن نقدر صبرك!

وأنا أتطلع إلى حذف تطبيق ASP.NET Core واستخدامه بدلاً من ذلك 😄

HashCode Combine العامة الثابتة (نص سلسلة ، مقارنة StringComparison) ؛
HashCode Combine العامة (نص سلسلة ، مقارنة StringComparison) ؛

Nit: الطرق الموجودة على String التي تتطلب StringComparison (على سبيل المثال ، Equals ، Compare ، StartsWith ، EndsWith ، إلخ. .) استخدم comparisonType كاسم للمعامل ، وليس comparison . هل يجب تسمية المعلمة comparisonType هنا أيضًا لتكون متسقة؟

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

كنقطة بيانات أخرى ، اختارت xUnit استخدام comparisonType أيضًا.

justinvp لقد أقنعتني. الآن بعد أن فكرت في الأمر حدسيًا ، "غير حساس لحالة الأحرف" أو "يعتمد على الثقافة" هو "نوع" من المقارنة. سأغير الاسم.

أنا موافق على شكل هذا ، ولكن فيما يتعلق بمقارنة StringComparison ، بديل محتمل:

لا تشمل:

ج #
HashCode Combine العامة الثابتة (نص سلسلة ، مقارنة StringComparison) ؛
HashCode Combine العامة (نص سلسلة ، مقارنة StringComparison) ؛

Instead, add a method:

``` C#
public class StringComparer
{
    public static StringComparer FromComparison(StringComparison comparison);
    ...
}

ثم بدلاً من الكتابة:

ج #
تجاوز عام int GetHashCode ()
{
إرجاع HashCode.Combine (_field1)
.Combine (_field2)
.Combine (_field3)
. الجمع (_field4 ، _ مقارنة) ؛
}

you write:

``` C#
public override int GetHashCode()
{
    return HashCode.Combine(_field1)
                   .Combine(_field2)
                   .Combine(_field3)
                   .Combine(StringComparer.FromComparison(_comparison).GetHashCode(_field4));
}

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

C# public override int GetHashCode() { return HashCode.Combine(_field1) .Combine(_field2) .Combine(_field3) .Combine(_comparer.GetHashCode(_field4)); }

stephentoub ، FromComparison تبدو فكرة جيدة. لقد اقترحت فعلاً تصاعدًا في السلسلة لإضافة string.GetHashCode(StringComparison) api ، مما يجعل مثالك أبسط (بافتراض سلسلة غير فارغة):

public override int GetHashCode()
{
    return HashCode.Combine(_field1)
                   .Combine(_field2)
                   .Combine(_field3)
                   .Combine(_field4.GetHashCode(_comparison));
}

Elion قال أنه أضاف مكالمات كثيرة للغاية.

(عدل: قدم اقتراحًا لواجهة برمجة التطبيقات الخاصة بك.)

أنا أيضًا لا أحب إضافة طريقتين متخصصتين على HashCode للسلسلة.
Eilon لقد ذكرت أن النمط مستخدم في ASP.NET Core نفسها. إلى أي مدى تعتقد أن المطورين الخارجيين سيستخدمونها؟

jamesqo شكرا لقيادة التصميم! كما قال terrajobst ، نحن نقدر مساعدتك وصبرك. قد تستغرق واجهات برمجة التطبيقات الصغيرة الأساسية بعض الوقت أحيانًا لتكرارها :).

دعنا نرى إلى أين وصلنا مع هذه القطعة الأخيرة من تعليقات API ، ثم يمكننا المضي قدمًا في التنفيذ.

يجب أن يكون هناك:

C# public static HashCode Combine<T>(T obj, IEqualityComparer<T> cmp);

؟

(أعتذر إذا تم رفض ذلك بالفعل وأنا أفتقده هنا).

stephentoub قال:

اكتب:

c# public override int GetHashCode() { return HashCode.Combine(_field1) .Combine(_field2) .Combine(_field3) .Combine(StringComparer.FromComparison(_comparison).GetHashCode(_field4)); }

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


حسنًا ، إنها ليست أطول قليلاً ، إنها مثل waaay لفترة أطول ، وليس لديها قابلية للاكتشاف.

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

إن الحصول على طريقة المساعد / التحويل الإضافي الثابت أمر جيد - على الرغم من أنني لست متأكدًا من أنني سأستخدمها - ولكن لماذا يتم ذلك على حساب طرق الراحة؟

لماذا على حساب وسائل الراحة؟

لأنه ليس من الواضح بالنسبة لي أن هناك حاجة حقًا لأساليب الراحة. أحصل على أن ASP.NET يفعل ذلك في أماكن مختلفة. كم عدد الأماكن؟ وكم عدد هذه الأماكن هل لديك في الواقع متغير StringComparison وليس لديك قيمة معروفة؟ في هذه الحالة ، لا تحتاج حتى إلى المساعد الذي ذكرته ويمكن أن يفعل ما يلي:

ج #
.Combine (StringComparer.InvariantCulture.GetHashCode (_field4))

which in no way seems onerous to me or any more undiscoverable than knowing about StringComparison and doing:

``` C#
.Combine(_field4, StringComparison.InvariantCulture);

وهو في الواقع أسرع ، حيث لا يتعين علينا التفرع داخل Combine للقيام بنفس الشيء الذي كان يمكن للمطور كتابته. هل الكود الإضافي يمثل الكثير من الإزعاج الذي يستحق إضافة أحمال زائدة متخصصة لتلك الحالة الواحدة؟ لماذا لا تفرط في StringComparer؟ لماذا لا تفرط في حساب EqualityComparer؟ لماذا لا تحمل الأحمال الزائدة التي تأخذ Func<T, int> ؟ في مرحلة ما ، ترسم الخط وتقول "القيمة التي يوفرها هذا التحميل الزائد لا تستحق العناء" ، لأن كل شيء نضيفه يأتي بتكلفة ، سواء كانت تكلفة الصيانة ، أو تكلفة حجم الكود ، أو تكلفة أي شيء ، وإذا كان المطور يحتاج حقًا إلى هذه الحالة ، فهو رمز إضافي ضئيل جدًا للمطور للتعامل مع عدد أقل من الحالات المتخصصة. لذلك كنت أقترح ربما المكان المناسب لرسم الخط قبل هذه الأحمال الزائدة وليس بعدها (ولكن كما ذكرت في بداية إجابتي السابقة ، "أنا موافق على شكل هذا" ، وكنت أقترح بديلاً) .

هذا هو البحث الذي قمت به: https://github.com/search؟p=2&q=user٪3Aaspnet+hashcodecombiner&type=Code&utf8=٪E2٪9C٪93

من بين حوالي 100 تطابق ، حتى من الصفحات القليلة الأولى فقط ، تحتوي كل حالة استخدام تقريبًا على سلاسل ، وفي العديد من الحالات تستخدم أنواعًا مختلفة من مقارنات السلاسل:

  1. ترتيبي: https://github.com/aspnet/Razor/blob/77ed9f22fc8894fbce796bb8a704d6cd03a3b226/src/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/TagHelperAttribute.cscriptorCom46
  2. Ordinal + IgnoreCase: https://github.com/aspnet/Razor/blob/bdbb854bdbde260b3c70f565a93ebbb185a7c5a7/src/Microsoft.AspNetCore.Razor/Compilation/TagHelpers/TagHelperRequiredA49
  3. ترتيبي: https://github.com/aspnet/Razor/blob/bdbb854bdbde260b3c70f565a93ebbb185a7c5a7/src/Microsoft.AspNetCore.Razor/Chunks/Generators/AttributeBlockChunkGenerator.cs#L58
  4. ترتيبي: https://github.com/aspnet/Razor/blob/77ed9f22fc8894fbce796bb8a704d6cd03a3b226/src/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/TagHelperDesignTimeDescriptor#41
  5. ترتيبي: https://github.com/aspnet/Razor/blob/dbcb6901209859e471c9aa978912cf7d6c178668/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AttributeBlockChunkGenerator.cs#L56
  6. ترتيبي: https://github.com/aspnet/Razor/blob/77ed9f22fc8894fbce796bb8a704d6cd03a3b226/src/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveTagHelper.cscriptor#
  7. Ordinal + IgnoreCase: https://github.com/aspnet/dnx/blob/bebc991012fe633ecac69675b2e892f568b927a5/src/Microsoft.Dnx.Tooling/NuGet/Core/PackageSource/PackageSource.cs#L107
  8. ترتيبي: https://github.com/aspnet/Razor/blob/bdbb854bdbde260b3c70f565a93ebbb185a7c5a7/src/Microsoft.AspNetCore.Razor/Tokenizer/Symbols/SymbolBase.cs#L52
  9. الترتيب الترتيبي: https://github.com/aspnet/Razor/blob/77ed9f22fc8894fbce796bb8a704d6cd03a3b226/src/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveTagHelperAttribute#39
  10. الترتيب الترتيبي: https://github.com/aspnet/Razor/blob/77ed9f22fc8894fbce796bb8a704d6cd03a3b226/src/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/TagHelperAttribute#LandesignTime

(وعشرات الآخرين).

لذلك يبدو أن هذا نمط شائع للغاية داخل قاعدة بيانات ASP.NET Core بالتأكيد. بالطبع لا يمكنني التحدث إلى أي نظام آخر.

من بين حوالي 100 مباراة

كل واحد من العشرة الذين أدرجتهم (لم ألقي نظرة على بقية البحث) يحدد بوضوح مقارنة السلسلة ، بدلاً من سحبها من متغير ، لذلك لا نتحدث فقط عن الفرق بين ، على سبيل المثال:

ج #
. الجمع بين (الاسم ، StringComparison.OrdinalIgnoreCase)

``` C#
.Combine(StringComparer.OrdinalIgnoreCase.GetHashCode(Name))

؟ هذا ليس "waaay لفترة أطول" وهو أكثر كفاءة ، ما لم أفقد شيئًا ما.

على أي حال ، كما ذكرت ، أقترح ببساطة أن نفكر حقًا فيما إذا كانت هذه الأحمال الزائدة ضرورية. إذا كان معظم الأشخاص يعتقدون أنهم كذلك ، ونحن لا نفكر فقط في قاعدة بيانات ASP.NET الخاصة بنا ، فلا بأس.

ذات صلة ، ما هو السلوك الذي نخطط له للمدخلات الفارغة؟ ماذا عن int == 0؟ يمكنني البدء في رؤية المزيد من الفوائد للحمل الزائد للسلسلة إذا سمحنا بتمرير قيمة null ، كما أعتقد أن StringComparer.GetHashCode عادةً ما يرمي لإدخال فارغ ، لذلك إذا كان هذا شائعًا حقًا ، فإنه يبدأ في أن يصبح أكثر تعقيدًا إذا كان المتصل لديه إلى حالة خاصة من القيم الخالية. ولكن هذا يطرح أيضًا السؤال عما سيكون عليه السلوك عند تقديم قيمة خالية. هل يتم خلط 0 في كود التجزئة كما هو الحال مع أي قيمة أخرى؟ هل يتم التعامل معها على أنها nop وترك رمز التجزئة بمفرده؟

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

في الواقع ، يأتي تصويتي من منظور قاعدة التعليمات البرمجية لـ ASP.NET Core ، حيث سيكون وجود حمل زائد مدرك للسلسلة مفيدًا للغاية. لم تكن الأشياء المتعلقة بطول الخط هي في الحقيقة شاغلي الرئيسي ، بل كانت تتعلق بقابلية الاكتشاف.

إذا لم يكن الحمل الزائد المدرك للسلسلة متاحًا في النظام ، فسنقوم فقط بإضافة طريقة ملحق داخلي في ASP.NET Core ونستخدمها.

إذا لم يكن الحمل الزائد المدرك للسلسلة متاحًا في النظام ، فسنقوم فقط بإضافة طريقة ملحق داخلي في ASP.NET Core ونستخدمها.

أعتقد أن هذا سيكون حلاً رائعًا في الوقت الحالي ، حتى نرى المزيد من الأدلة على أن واجهة برمجة التطبيقات هذه مطلوبة بشكل عام ، وأيضًا خارج قاعدة التعليمات البرمجية الأساسية لـ ASP.NET.

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

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

  1. نجعل سيناريوهات مثل Eilon أسهل بكثير.
  2. نجعل الأمر قابلاً للاكتشاف أن النظر في مقارنة السلاسل أمر مهم ، وخاصة الغلاف.

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

ومع ذلك ، إذا كان الشاغل الأساسي يتعلق بغلاف خاص string ، فماذا عن هذا:

ج #
HashCode الهيكل العام: IEquatable
{
HashCode العام(T obj ، IEqualityComparerالمقارنة) ؛
}

// الاستخدام
إرجاع HashCode.Combine (_numberField)
.Combine (_stringField، StringComparer.OrdinalIgnoreCase) ؛
""

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

(تحرير: أعتقد أنه يجب علي حقًا الفضل في ذلك إلى JonHanna لأنه ذكره مسبقًا في الموضوع؟ 😄)

JonHanna نعم ، سنقوم أيضًا

آسف لمقاطعة المحادثة هنا. لكن أين أضع النوع الجديد؟ mellinoeericstjweshaggard، التي تقترحونها أقوم بإجراء الجمعية الجديدة / حزمة لهذا النوع مثل System.HashCode ، أو ينبغي أن إضافته إلى القائمة تجميع مثل System.Runtime.Extensions ؟ شكرا.

لقد قمنا مؤخرًا بإعادة تشكيل تخطيط التجميع في .NET Core قليلاً ؛ أقترح وضعه في المكان الذي تعيش فيه المقارنات الملموسة ، والتي يبدو أنها تشير إلى System.Runtime.Extensions .

weshaggard؟

terrajobst فيما يتعلق بالاقتراح نفسه ، اكتشفت للتو أنه لا يمكننا تسمية كل من التحميل الزائد الثابت والمثيل Combine ، للأسف. 😢

ينتج عن التالي خطأ في

using System;
using System.Collections.Generic;

public struct HashCode
{
    public void Combine(int i)
    {
    }

    public static void Combine(int i)
    {
    }
}

الآن لدينا خياران:

  • أعد تسمية الأحمال الزائدة الثابتة إلى شيء مختلف مثل Create ، Seed ، إلخ.
  • انقل الأحمال الزائدة الثابتة إلى فئة ثابتة أخرى:
public static class Hash
{
    public static HashCode Combine(int hash);
}

public struct HashCode
{
    public HashCode Combine(int hash);
}

// Usage:
return Hash.Combine(_field1)
           .Combine(_field2)
           .Combine(_field3);

أنا أفضلية تجاه الثانية. من المؤسف أنه يتعين علينا حل هذه المشكلة ، لكن ... الأفكار؟

يبدو فصل المنطق إلى نوعين غريبًا بالنسبة لي - لاستخدام HashCode عليك إجراء الاتصال والبدء بـ Hash class بدلاً من ذلك.

أفضل إضافة طريقة Create (أو Seed أو Init ).
أود أن أضيف أيضًا تحميل no-args الزائد HashCode.Create().Combine(_field1).Combine(_field2) .

karelz ، لا أعتقد أنه يجب علينا إضافة طريقة المصنع إذا لم تكن بنفس الاسم. يجب أن نقدم فقط المُنشئ بدون معلمات ، new ، لأنه طبيعي أكثر. بالإضافة إلى ذلك ، لا يمكن أن تمنع e الناس من كتابة new HashCode().Combine لأنها بنية.

public override int GetHashCode()
{
    return new HashCode()
        .Combine(_field1)
        ...
}

يؤدي هذا إلى دمج إضافي مع كود التجزئة الخاص بـ 0 و _field1 ، بدلاً من التهيئة مباشرةً من كود التجزئة. ومع ذلك ، فإن أحد الآثار الجانبية للتجزئة الحالية التي نستخدمها ، هو أن الرقم 0 يتم تمريره باعتباره المعامل الأول ، وسيتم تدويره إلى الصفر وإضافته إلى الصفر. وعندما يتم xored 0 باستخدام كود التجزئة الأول ، فإنه سينتج فقط كود التجزئة الأول. لذلك إذا كان JIT جيدًا في الطي المستمر (وأعتقد أنه يحسن هذا xor بعيدًا) ، في الواقع يجب أن يكون هذا مكافئًا للتهيئة المباشرة.

واجهة برمجة التطبيقات المقترحة (المواصفات المحدثة):

namespace System
{
    public struct HashCode : IEquatable<HashCode>
    {
        public HashCode Combine(int hash);
        public HashCode Combine<T>(T obj);
        public HashCode Combine<T>(T obj, IEqualityComparer<T> comparer);

        public int Value { get; }

        public static implicit operator int(HashCode hashCode);

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

        public override bool Equals(object obj);
        public override bool Equals(HashCode other);
        public override int GetHashCode();
    }
}

redknightloisJonHannastephentoubEilon، هل لديك رأي حول طريقة مصنع مقابل باستخدام منشئ افتراضي؟ اكتشفت أن المترجم لا يسمح بزيادة تحميل Combine ثابتًا لأن ذلك يتعارض مع طرق المثيل ، لذلك لدينا خيار إما

HashCode.Create(field1).Combine(field2) // ...

// or, using default constructor

new HashCode().Combine(field1).Combine(field2) // ...

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

الاحتمال الآخر هو نوعان مختلفان ، أحدهما بمصنع Combine ، أحدهما مع مثيل Combine (أو الثاني كملحق للنوع الأول).

لست متأكدًا مما سأفضله TBH.

JonHanna ، فكرتك الثانية مع زيادة حمل المثيل hc.Combine(obj) في هذه الحالة التقاط الحمل الزائد الثابت: TryRoslyn .

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

يبدو فصل المنطق إلى نوعين غريبًا بالنسبة لي - لاستخدام HashCode ، عليك إجراء الاتصال والبدء بفئة Hash بدلاً من ذلك.

ما هو الاتصال الذي يجب على الناس القيام به؟ ألن نقدم لهم Hash أولاً ، ثم من هناك يمكنهم شق طريقهم إلى HashCode ؟ لا أعتقد أن إضافة فئة ثابتة جديدة ستكون مشكلة.

يبدو فصل المنطق إلى نوعين غريبًا بالنسبة لي - لاستخدام HashCode ، عليك إجراء الاتصال والبدء بفئة Hash بدلاً من ذلك.

يمكننا الاحتفاظ بنوع المستوى الأعلى HashCode ووضع الهيكل فقط. سيسمح هذا بالاستخدام المرغوب مع الاحتفاظ بـ "نقطة دخول" واجهة برمجة التطبيقات لنوع واحد من المستوى الأعلى ، على سبيل المثال:

ج #
نظام مساحة الاسم
{
فئة HashCode العامة الثابتة
{
HashCodeValue Combine العامة الثابتة (int التجزئة) ؛
HashCodeValue Combine العامة الثابتة(T obj) ؛
HashCodeValue Combine العامة الثابتة(T obj ، IEqualityComparerالمقارنة) ؛

    public struct HashCodeValue : IEquatable<HashCodeValue>
    {
        public HashCodeValue Combine(int hash);
        public HashCodeValue Combine<T>(T obj);
        public HashCodeValue Combine<T>(T obj, IEqualityComparer<T> comparer);

        public int Value { get; }

        public static implicit operator int(HashCodeValue hashCode);

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

        public bool Equals(HashCodeValue other);
        public override bool Equals(object obj);
        public override int GetHashCode();
    }
}

}
""

تحرير: على الرغم من أنك ربما تحتاج إلى اسم أفضل من HashCodeValue للنوع المتداخل إذا ذهبنا في هذا المسار لأن HashCodeValue.Value زائدة عن الحاجة قليلاً ، فلن يتم استخدام Value كثيرًا غالبا. ربما لا نحتاج حتى إلى خاصية Value - يمكنك الحصول على Value عبر GetHashCode() إذا كنت لا تريد الإرسال إلى int .

justinvp ما هي مشكلة وجود نوعين منفصلين في المقام الأول؟ يبدو أن هذا النظام يعمل بشكل جيد مقابل LinkedList<T> و LinkedListNode<T> ، على سبيل المثال.

ما هي المشكلة في وجود نوعين منفصلين في المقام الأول؟

هناك نوعان من المخاوف مع نوعين من المستوى الأعلى:

  1. ما هو نوع "نقطة الدخول" لواجهة برمجة التطبيقات؟ إذا كانت الأسماء هي Hash و HashCode ، فما الاسم الذي تبدأ به؟ ليس واضحا من تلك الأسماء. مع LinkedList<T> و LinkedListNode<T> من الواضح تمامًا أيهما هو نقطة الدخول الرئيسية ، LinkedList<T> ، وأيهما المساعد.
  2. تلويث مساحة الاسم System . إنه ليس مصدر قلق كبير مثل (1) ، ولكنه شيء يجب أخذه في الاعتبار عندما نفكر في الكشف عن وظائف جديدة في مساحة الاسم System .

يساعد التعشيش في التخفيف من هذه المخاوف.

تضمين التغريدة

ما هو نوع "نقطة الدخول" لواجهة برمجة التطبيقات؟ إذا كانت الأسماء هي Hash و HashCode ، فما الاسم الذي تبدأ به؟ ليس واضحا من تلك الأسماء. مع LinkedListو LinkedListNodeمن الواضح تمامًا أيهما هو نقطة الدخول الرئيسية ، LinkedList، ومن هو المساعد.

حسنًا ، نقطة كافية. ماذا لو قمنا بتسمية الأنواع Hash و HashValue ، وليس الأنواع المتداخلة؟ هل هذا يدل على وجود علاقة قهرية كافية بين النوعين؟

إذا فعلنا ذلك ، فإن طريقة المصنع تصبح مقتضبة: Hash.Combine(field1).Combine(field2) . بالإضافة إلى ذلك ، فإن استخدام نوع البنية في حد ذاته لا يزال عمليًا. على سبيل المثال ، قد يرغب شخص ما في جمع قائمة من التجزئة ، وإبلاغ القارئ بذلك ، يتم استخدام List<HashValue> بدلاً من List<int> . قد لا يعمل هذا بشكل جيد إذا جعلنا النوع متداخلًا: List<HashCode.HashCodeValue> (حتى List<Hash.Value> أمر محير نوعًا ما للوهلة الأولى).

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

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

ربما لا نحتاج حتى إلى خاصية القيمة - يمكنك الحصول على القيمة عبر GetHashCode () إذا كنت لا تريد الإرسال إلى int.

فكرت في ذلك في وقت سابق. ولكن كيف يعرف المستخدم أن النوع يتجاوز GetHashCode ، أو أن له عامل تشغيل ضمني؟

API المقترحة

public static class Hash
{
    public static HashValue Combine(int hash);
    public static HashValue Combine<T>(T obj);
    public static HashValue Combine<T>(T obj, IEqualityComparer<T> comparer);
}

public struct HashValue : IEquatable<HashValue>
{
    public HashValue Combine(int hash);
    public HashValue Combine<T>(T obj);
    public HashValue Combine<T>(T obj, IEqualityComparer<T> comparer);

    public int Value { get; }

    public static implicit operator int(HashValue hashValue);

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

    public override bool Equals(object obj);
    public bool Equals(HashValue other);
    public override int GetHashCode();
}

ماذا لو قمنا بتسمية الأنواع Hash و HashValue ، وليس الأنواع المتداخلة؟

Hash يبدو لي اسمًا عامًا جدًا. أعتقد أننا بحاجة إلى وجود HashCode في اسم واجهة برمجة التطبيقات الخاصة بنقطة الإدخال لأن الغرض المقصود منها هو المساعدة في تنفيذ GetHashCode() ، وليس GetHash() .

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

تبدو هذه حالة استخدام غير مرجحة - لست متأكدًا من أنه يجب علينا تحسين التصميم لها.

واجهات برمجة تطبيقات BCL الوحيدة التي يمكنني التفكير فيها حيث لدينا أنواع متداخلة

TimeZoneInfo.AdjustmentRule و TimeZoneInfo.TransitionTime أمثلة في BCL تمت إضافتها عن قصد كأنواع متداخلة.

تضمين التغريدة

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

👍 فهمت.

لقد فكرت في الأشياء أكثر قليلا. يبدو من المعقول أن يكون لديك بنية متداخلة ؛ كما ذكرت ، لن يرى معظم الأشخاص النوع الفعلي أبدًا. شيء واحد فقط: أعتقد أنه يجب تسمية النوع Seed بدلاً من HashCodeValue . سياق اسمها ضمني بالفعل من قبل الفئة التي تحتوي عليها.

API المقترحة

namespace System
{
    public static class HashCode
    {
        public static Seed Combine(int hash);
        public static Seed Combine<T>(T obj);
        public static Seed Combine<T>(T obj, IEqualityComparer<T> comparer);

        public struct Seed : IEquatable<Seed>
        {
            public Seed Combine(int hash);
            public Seed Combine<T>(T obj);
            public Seed Combine<T>(T obj, IEqualityComparer<T> comparer);

            public int Value { get; }

            public static implicit operator int(Seed seed);

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

            public bool Equals(Seed other);
            public override bool Equals(object obj);
            public override int GetHashCode();
        }
    }
}

jamesqo هل هناك أي اعتراض أو مشكلة في التنفيذ بسبب وجود public readonly int Value بدلاً من ذلك؟ مشكلة Seed هي أنه ليس من الناحية الفنية بذرة بعد الجمع الأول.

توافق أيضًا مع justinvp ، يجب حجز Hash للتعامل مع التجزئة. تم تقديم هذا تبسيط التعامل مع HashCode بدلا من ذلك.

redknightlois للتوضيح ، كنا نتحدث عن اسم البنية وليس اسم الخاصية.

        public struct Seed : IEquatable<Seed>
        {
            public Seed Combine(int hash);
            public Seed Combine<T>(T obj);
            public Seed Combine<T>(T obj, IEqualityComparer<T> comparer);

            public int Value { get; }

            public static implicit operator int(Seed seed);

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

            public bool Equals(Seed other);
            public override bool Equals(object obj);
            public override int GetHashCode();
        }

استعمال:
c# int hashCode = HashCode.Combine(field1).Combine(name, StringComparison.OrdinalIgnoreCase).Value; int hashCode = (int)HashCode.Combine(field1).Combine(field2);

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

وهي بذرة للحصاد التالي الذي ينتج بذرة جديدة.

هل هناك أي اعتراض أو مشكلة في التنفيذ تتعلق بوجود قيمة int العامة للقراءة فقط بدلاً من ذلك؟

لماذا ا؟ int Value { get; } أكثر اصطلاحًا ويمكن تضمينه بسهولة.

وهي بذرة للحصاد التالي الذي ينتج بذرة جديدة.

ألن تكون هذه بذرة؟ ؛)

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

تحرير: بالإضافة إلى أنه يدفع أيضًا بفكرة أن هذه الهياكل غير قابلة للتغيير حقًا.

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

إذا وجدت بنية واحدة غير تصحيح الأخطاء حيث لا تكون خاصية التنفيذ التلقائي مضمنة دائمًا ، فهذه مشكلة JIT ويجب بالتأكيد إصلاحها.

يُترجم أيضًا حقل للقراءة فقط لـ int واحدة على بنية مباشرة في السجل

هناك تحسينات يمكن السماح بها لأنها يمكن أن تجعلها للقراءة فقط.

سيكون مجال دعم هذا الهيكل للقراءة فقط ؛ ستكون واجهة برمجة التطبيقات (API) أداة وصول.

لا أعتقد أن استخدام خاصية سيضر بالأداء بأي شكل من الأشكال هنا.

jamesqo سأضع ذلك في الاعتبار ، عندما أجد هؤلاء. بالنسبة إلى التعليمات البرمجية الحساسة للأداء ، لم أعد أستخدم الخصائص بعد الآن بسبب ذلك (ذاكرة العضلات في هذه المرحلة).

قد تفكر في تسمية البنية المتداخلة بـ "State" بدلاً من "Seed"؟

ellismg بالتأكيد ، شكرًا على الاقتراح. كنت أعاني من أجل ابتكار اسم جيد للبنية الداخلية.

karelz أعتقد أن واجهة برمجة التطبيقات هذه جيدة أخيرًا ؛ هذه المرة ، راجعت للتأكد من تجميع كل شيء. ما لم يكن لدى أي شخص أي اعتراض ، سأبدأ العمل على تنفيذ ذلك.

jamesqoJonHanna لماذا نحن بحاجة إلى Combine<T>(T obj) بدلا من Combine(object o) ؟

لماذا نحتاج إلى الجمع(T obj) بدلاً من Combine (object o)؟

سيخصص الأخير إذا كان المثيل عبارة عن هيكل.

دوه ، شكرا للتوضيح.

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

ما لم تكن هناك معارضة قوية ، فهذا هو التصميم الذي اتفقنا عليه:

ج #
نظام مساحة الاسم
{
HashCode الهيكل العام: IEquatable
{
إنشاء HashCode عام ثابت (int hashCode) ؛
إنشاء HashCode العام الثابت(T obj) ؛
إنشاء HashCode العام الثابت(T obj ، IEqualityComparerالمقارنة) ؛

    public HashCode Combine(int hashCode);
    public HashCode Combine<T>(T obj);
    public HashCode Combine<T>(T obj, IEqualityComparer<T> comparer);

    public int Value { get; }

    public static implicit operator int(HashCode hashCode);

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

    public bool Equals(HashCode other);
    public override bool Equals(object obj);
    public override int GetHashCode();
}

}
""

دعنا ننتظر يومين للحصول على تعليقات إضافية لمعرفة ما إذا كانت هناك تعليقات قوية على الاقتراح المعتمد. ثم يمكننا جعلها "جاهزة للإمساك بها".

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

أعتقد أن الجمع بين Create and Combine أسوأ.

يرجى مراجعة https://github.com/dotnet/corefx/issues/8034#issuecomment -262661653

تضمين التغريدة

نعتقد أن إعادة التسمية إلى Create هي الأكثر منطقية لأنها تنشئ رمزًا قابلاً للقراءة إلى حد ما ، مقارنةً باستخدام المُنشئ الافتراضي.

ما لم تكن هناك معارضة قوية ، فهذا هو التصميم الذي اتفقنا عليه:

أسمعك ، لكن فكرت في اللحظة الأخيرة أثناء عملي على التنفيذ ... هل يمكننا ببساطة إضافة خاصية ثابتة Zero / Empty إلى HashCode ، ثم اطلب من الناس الاتصال بـ Combine من هناك؟ سيحررنا ذلك من الاضطرار إلى وجود طرق منفصلة لـ Combine / Create .

namespace System
{
    public struct HashCode : IEquatable<HashCode>
    {
        public static HashCode Empty { get; }

        public HashCode Combine(int hashCode);
        public HashCode Combine<T>(T obj);
        public HashCode Combine<T>(T obj, IEqualityComparer<T> comparer);

        public int Value { get; }

        public static implicit operator int(HashCode hashCode);

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

        public bool Equals(HashCode other);
        public override bool Equals(object obj);
        public override int GetHashCode();
    }
}

int GetHashCode()
{
    return HashCode.Empty
        .Combine(_1)
        .Combine(_2);
}

أي شخص آخر يعتقد أن هذه فكرة جيدة؟ (سأقدم PR في هذه الأثناء ، وإذا اعتقد الناس ذلك فسأغيره في العلاقات العامة.)

jamesqo ، أحب فكرة Empty / Zero.

وأود أن يكون على ما يرام مع ذلك (أي تفضيل قوي بين Empty مقابل Create المصنع) ...weshaggardbartonjsstephentoubterrajobst ماذا يعتقد الرجال؟

أنا شخصياً أعتقد أن إنشاء () أفضل ؛ لكني أحب HashCode.Empty أفضل من new HashCode() .

نظرًا لأنه يسمح بإصدار لا يحتوي على عامل جديد ، ولا يمنعنا من اتخاذ قرار لاحقًا بأننا نريد حقًا إنشاء برنامج تمهيد ... :: shrug ::.

هذا هو المدى الكامل للرد (المعروف أيضًا باسم ليس كثيرًا).

FWIW سأصوت لـ Create بدلاً من Empty / Zero . أفضل البدء بقيمة فعلية بدلاً من تعليق كل شيء على Empty / Zero . إنه يشعر / يبدو غريبًا.

كما أنه لا يشجع الناس على البذر من الصفر ، والذي يميل إلى أن يكون بذرة فقيرة.

أفضل "إنشاء" بدلاً من إفراغ. إنها تتوافق مع طريقة تفكيري حولها: أريد إنشاء كود تجزئة وخلط قيم إضافية. سأكون على ما يرام مع النهج المتداخل أيضًا.

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

JonHanna فقط للتوضيح: لقد قصدت ذلك باعتباره تصويتًا لـ Create ، أليس كذلك؟

وفي رابع فكر ماذا عن "مع" بدلاً من "إنشاء".

HashCode. مع (أ). الجمع بين (ب). تجمع (ج)

مثال على الاستخدام بناءً على أحدث مناقشة (مع احتمال استبدال Create مع اسم بديل):

ج #
تجاوز العامة int GetHashCode () =>
HashCode.Create (_field1) .Combine (_field2) .Combine (_field3) ؛

We went down the path of this chaining approach, but didn't reconsider earlier proposals when the static & instance `Combine` methods didn't pan out...

Are we sure we don't want something like the existing `Path.Combine` pattern, that was proposed previously, with a handful of generic `Combine` overloads? e.g.:

```c#
public override int GetHashCode() =>
    HashCode.Combine(_field1, _field2, _field3);

justinvp من شأنه أن يؤدي إلى رمز غير متسق + المزيد من النكات أعتقد ب / ج من مجموعات أكثر عمومية. يمكننا دائمًا إعادة النظر في هذا الأمر في قضية أخرى إذا اتضح أنه مرغوب فيه.

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

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

ماذا لو فعلنا شيئًا ما على غرار برنامج ASP.NET Core المتغير HashCodeCombiner "builder" ، مع طرق مماثلة Add ، لكننا ضمّننا أيضًا دعمًا لبناء جملة مُهيئ المجموعة؟

استعمال:

ج #
تجاوز العامة int GetHashCode () =>
HashCode الجديد {_field1، _field2، _field3}؛

With a surface area something like:

```c#
namespace System
{
    public struct HashCode : IEquatable<HashCode>, IEnumerable
    {
        public void Add(int hashCode);
        public void Add<T>(T obj);
        public void Add<T>(T obj, IEqualityComparer<T> comparer);

        public int Value { get; }

        public static implicit operator int(HashCode hashCode);

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

        public bool Equals(HashCode other);
        public override bool Equals(object obj);
        public override int GetHashCode();

        IEnumerator IEnumerable.GetEnumerator();
    }
}

سيتعين عليها تنفيذ IEnumerable كحد أدنى مع طريقة Add واحدة على الأقل لتمكين بناء جملة مُهيئ المجموعة. يمكن تنفيذ IEnumerable بشكل صريح لإخفائه من التحسس و GetEnumerator يمكن أن يرمي NotSupportedException أو يُرجع قيمة كود التجزئة كعنصر واحد مدمج في العدد ، إذا حدث أي شخص استخدمه (وهو أمر نادر الحدوث).

justinvp ، لديك فكرة مثيرة للاهتمام. ومع ذلك ، فأنا أعارض بكل احترام. أعتقد أن HashCode يجب أن يظل ثابتًا لتجنب التعثر مع الهياكل القابلة للتغيير. كما أن الاضطرار إلى تنفيذ IEnumerable لهذا يبدو مصطنعًا / غير مستقر ؛ إذا كان لدى شخص ما توجيه using System.Linq في الملف ، فسيظهر Cast<> و OfType<> كطرق امتداد إذا وضعوا نقطة بجوار HashCode . أعتقد أننا يجب أن نلتزم بالاقتراح الحالي.

jamesqo ، أوافق - ومن هنا ترددت في ذكرها. الشيء الوحيد الذي يعجبني فيه هو أن الاستخدام يمكن أن يكون أنظف من التسلسل ، ولكن هذا بحد ذاته يمثل عيبًا آخر لأنه ليس من الواضح أنه يمكن استخدام مُهيئ المجموعة حتى دون رؤية استخدام العينة.

MadsTorgersen ، jaredpar ، لماذا يتطلب مُهيئ المجموعة تنفيذ IEnumerable \@ تعليق justinvp الثالث أعلاه.

jamesqo ، أوافق على أنه من الأفضل الحفاظ على هذا غير قابل للتغيير (وليس IEnumerable \

mellinoe أعتقد أن هذا سيجعل الحالة البسيطة أبسط قليلاً ، لكنه سيجعل أي شيء يتجاوز ذلك أكثر تعقيدًا (وأقل وضوحًا بشأن الشيء الصحيح الذي يجب فعله).

يتضمن:

  1. من العناصر التي لديك زائدة ل
  2. الظروف
  3. الحلقات
  4. باستخدام المقارنة

ضع في اعتبارك الكود من ASP.NET المنشور من قبل حول هذا الموضوع (محدث للاقتراح الحالي):

ج #
var hashCode = HashCode
إنشاء (IsMainPage)
. الجمع بين (ViewName، StringComparer.Ordinal)
.Combine (ControllerName ، StringComparer.Ordinal)
.Combine (AreaName، StringComparer.Ordinal) ؛

إذا (ViewLocationExpanderValues! = خالية)
{
foreach (عنصر var في ViewLocationExpanderValues)
{
hashCode = hashCode
. الجمع بين (البند. المفتاح ، StringComparer.Ordinal)
الجمع بين (item.Value و StringComparer.Ordinal) ؛
}
}

إرجاع hashCode ؛

How would this look with the original `Hash.CombineHashCodes`? I think it would be:

```c#
var hashCode = Hash.CombineHashCodes(
    IsMainPage,
    StringComparer.Ordinal.GetHashCode(ViewName),
    StringComparer.Ordinal.GetHashCode(ControllerName),
    StringComparer.Ordinal.GetHashCode(AreaName));

if (ViewLocationExpanderValues != null)
{
    foreach (var item in ViewLocationExpanderValues)
    {
        hashCode = Hash.CombineHashCodes(
            hashCode
            StringComparer.Ordinal.GetHashCode(item.Key),
            StringComparer.Ordinal.GetHashCode(item.Value));
    }
}

return hashCode;

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

KrzysztofCwalina وفقًا لملاحظةericlippert في لغة البرمجة C # 1 ، فذلك لأن مُهيِّئات المجموعة (بشكل غير مفاجئ) يُقصد بها أن تكون بناء الجملة لإنشاء المجموعة ، وليس الحساب (الذي كان الاستخدام الشائع الآخر للطريقة المسماة Add ).

1 نظرًا لكيفية عمل كتب Google ، قد لا يعمل هذا الرابط مع الجميع.

KrzysztofCwalina ، لاحظ أنه يتطلب IEnumerable ، وليس IEnumerable<T> .

svick ، قاصر في المثال الأول أعلاه: الاستدعاء الأول لـ .Combine سيكون .Create مع الاقتراح الحالي. ما لم نستخدم النهج المتداخل.

svick

من شأنه أيضًا أن يجعل أي شيء يتجاوز ذلك أكثر تعقيدًا (وأقل وضوحًا بشأن الشيء الصحيح الذي يجب فعله)

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

justinvp شكرا ، محدث. (ذهبت مع الاقتراح الأول في المنشور الأول ، ولم أدرك أنه قديم ، ربما يجب على شخص ما تحديثه.)

mellinoe المشكلة هي في الواقع أن الثانية يمكن أن تولد أخطاء خفية. هذا هو الكود الفعلي لأحد مشاريعنا.

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int GetHashCode(PageFromScratchBuffer obj)
        {
            int v = Hashing.Combine(obj.NumberOfPages, obj.ScratchFileNumber);
            int w = Hashing.Combine(obj.Size.GetHashCode(), obj.PositionInScratchBuffer.GetHashCode());
            return Hashing.Combine(v, w);            
        }

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

ذهبت مع الاقتراح الأول في المنشور الأول ، ولم أدرك أنه قديم ، فمن المحتمل أن يقوم شخص ما بتحديثه.

فعله.
راجع للشغل: من الصعب جدًا تتبع هذا الاقتراح ، خاصة الأصوات ... العديد من الاختلافات (التي أعتقد أنها جيدة ؛-))

karelz إذا أضفنا واجهات برمجة تطبيقات Create ، فأعتقد أنه لا يزال بإمكاننا إضافة Empty . لا يجب أن يكون أحدهما أو الآخر ، كما قال bartonjs . مقترح

namespace System
{
    public struct HashCode : IEquatable<HashCode>
    {
        public HashCode();

        public static HashCode Empty { get; }

        public static HashCode Create(int hashCode);
        public static HashCode Create<T>(T value);
        public static HashCode Create<T>(T value, IEqualityComparer<T> comparer);

        public HashCode Combine(int hashCode);
        public HashCode Combine<T>(T value);
        public HashCode Combine<T>(T value, IEqualityComparer<T> comparer);

        public int Value { get; }

        public static implicit operator int(HashCode hashCode);

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

        public bool Equals(HashCode other);
        public override bool Equals(object obj);
        public override int GetHashCode();
        public override string ToString();
    }
}

تضمين التغريدة

كما أنه لا يشجع الناس على البذر من الصفر ، والذي يميل إلى أن يكون بذرة فقيرة.

ستكون خوارزمية التجزئة التي نختارها هي نفسها المستخدمة في HashHelpers اليوم ، والتي لها تأثير hash(0, x) == x . سينتج عن HashCode.Empty.Combine(x) نفس النتائج تمامًا مثل HashCode.Create(x) ، لذلك لا يوجد فرق موضوعيًا.

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

@ karelz شكرا لاكتشاف ، ثابتة.

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

public static HashCode Create(int hash);
public HashCode Combine(int hash);

هل يجب تسمية المعلمة hashCode بدلاً من hash نظرًا لأن القيمة التي تم تمريرها ستكون رمز تجزئة يُحتمل الحصول عليه من استدعاء GetHashCode() ؟

Empty / Zero

إذا انتهينا من الاحتفاظ بهذا ، فإن اسمًا آخر يجب مراعاته هو Default .

تضمين التغريدة

هل يجب تسمية المعلمة hashCode بدلاً من التجزئة نظرًا لأن القيمة التي تم تمريرها ستكون رمز تجزئة من المحتمل الحصول عليه من استدعاء GetHashCode ()؟

أردت تسمية المعلمات int hash HashCode والمعلمات hashCode HashCode hashCode . ومع ذلك ، عند التفكير الثاني ، أعتقد أن hashCode سيكون أفضل لأنه ، كما ذكرت ، hash غامض نوعًا ما. سوف أقوم بتحديث API.

إذا انتهينا من الاحتفاظ بهذا ، هناك اسم آخر يجب مراعاته وهو افتراضي.

عندما أسمع Default أفكر في "الطريقة المعتادة للقيام بشيء ما عندما لا تعرف الخيار الذي تختاره" ، وليس "القيمة الافتراضية للبنية." على سبيل المثال ، شيء مثل Encoding.Default له دلالة مختلفة تمامًا.

ستكون خوارزمية التجزئة التي نختارها هي نفسها المستخدمة في HashHelpers اليوم ، والتي لها تأثير التجزئة (0 ، x) == x. HashCode.Empty.Combine (x) ستنتج نفس النتائج تمامًا مثل HashCode.Create (x) ، لذلك لا يوجد فرق موضوعي.

باعتباري شخصًا لا يعرف الكثير عن العناصر الداخلية لهذا ، فأنا حقًا أحب بساطة HashCode.Create(x).Combine(...) . Create واضح جدًا ، لأنه يُستخدم في العديد من الأماكن الأخرى.

إذا لم يوفر Empty / Zero / Default أي استخدام خوارزمي ، فلا ينبغي أن يكون هناك IMO.

ملاحظة: موضوع مثير جدا للاهتمام !! أحسنت! 👍

تضمين التغريدة

إذا لم يوفر Empty / Zero / Default أي استخدام خوارزمي ، فلا ينبغي أن يكون هناك IMO.

يوفر وجود حقل Empty استخدامًا حسابيًا. إنه يمثل "قيمة ابتدائية" يمكنك من خلالها دمج التجزئة. على سبيل المثال ، إذا كنت تريد دمج مجموعة من التجزئة باستخدام Create بدقة ، فهذا مؤلم جدًا:

int CombineRange(int[] hashes)
{
    if (hashes.Length == 0)
    {
        return 0;
    }

    var result = HashCode.Create(hashes[0]);

    for (int i = 1; i < hashes.Length; i++)
    {
        result = result.Combine(hashes[i]);
    }

    return result;
}

إذا كان لديك Empty ، فسيصبح الأمر أكثر طبيعية:

int CombineRange(int[] hashes)
{
    var result = HashCode.Empty;

    for (int i = 0; i < hashes.Length; i++)
    {
        result = result.Combine(hashes[i]);
    }

    return result;
}

// or

int CombineRange(int[] hashes)
{
    return hashes.Aggregate(HashCode.Empty, (hc, next) => hc.Combine(next));
}

terrajobst هذا النوع مشابه جدًا لـ ImmutableArray<T> بالنسبة لي. المصفوفة الفارغة ليست مفيدة جدًا في حد ذاتها ، ولكنها مفيدة جدًا "كنقطة انطلاق" للعمليات الأخرى ، ولهذا السبب لدينا خاصية Empty لها. أعتقد أنه سيكون من المنطقي أن يكون لديك واحد مقابل HashCode أيضًا ؛ نحن نحتفظ بـ Create .

jamesqo لقد لاحظت أنك بصمت / عن طريق الصدفة غيرت اسم arg obj إلى value في عرضك https://github.com/dotnet/corefx/issues/8034#issuecomment -262661653. لقد قمت بالتبديل مرة أخرى إلى obj حيث تلتقط IMO ما تحصل عليه بشكل أفضل. الاسم value أكثر ارتباطًا بقيمة التجزئة "int" نفسها في هذا السياق.
أنا منفتح لمزيد من المناقشة حول اسم الحجة إذا لزم الأمر ، ولكن دعونا نغيره عن قصد ونتتبع الاختلاف مقابل الاقتراح الأخير المعتمد.

لقد قمت بتحديث الاقتراح في الأعلى. لقد طلبت أيضًا من الاختلافات ضد آخر نسخة تمت الموافقة عليها من الاقتراح.

ستكون خوارزمية التجزئة التي نختارها هي نفسها المستخدمة في HashHelpers اليوم

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

ماذا لو فعلنا شيئًا على غرار منشئ HashCodeCombiner القابل للتغيير الخاص بـ ASP.NET Core

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

لماذا هي خوارزمية جيدة لاختيار الخوارزمية التي يجب استخدامها في كل مكان؟

لا ينبغي استخدامه في كل مكان. انظر تعليقي على https://github.com/dotnet/corefx/issues/8034#issuecomment -260790829 ؛ إنه يستهدف بشكل أساسي الأشخاص الذين لا يعرفون الكثير عن التجزئة. يمكن للأشخاص الذين يعرفون ما يفعلونه تقييمه لمعرفة ما إذا كان يناسب احتياجاتهم.

ما الافتراض الذي ستفعله حول دمج رموز التجزئة؟ إذا تم استخدامه في كل مكان ، فهل سيفتح طرقًا جديدة لهجمات DDoS؟

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

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

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

لا أعتقد أنه يجب أن يكون هناك مُدمج عالمي بحجم هيكل كبير يحاول أن يلائم كل حالة استخدام.

هل سيتمكن ASP.NET Core من استبدال أداة دمج رموز التجزئة الخاصة بها - التي تحتوي على 64 بت من الحالة حاليًا - بهذا المجمع؟

كنت أتخيل أنواع أكواد تجزئة منفصلة وكلها ذات حجم صغير (FnvHashCode ، إلخ.)

ألا يؤدي هذا إلى انفجار اندماجي؟ يجب أن يكون جزءًا من اقتراح API لتوضيح ما يؤدي إليه تصميم واجهة برمجة التطبيقات هذا.

jkotas لقد ذكرت اعتراضات مماثلة في بداية المناقشة. يتطلب التعامل مع وظائف التجزئة معرفة بالموضوع. لكنني أفهم وأدعم إصلاح المشكلة التي تسببت في عام 2001 من خلال إدخال رموز التجزئة في جذر إطار العمل ولا أصف وصفة لدمج التجزئة. يهدف هذا التصميم إلى حل ذلك بالنسبة لـ 99٪ من الحالات (حيث لا تتوفر معرفة بالموضوع أو حتى ضرورية ، نظرًا لأن الخصائص الإحصائية للتجزئة جيدة بدرجة كافية). يجب أن يكون ASP.Net Core قادرًا على استخدام تضمين مثل هذه المجمعات في إطار عمل للأغراض العامة على مجموعة غير نظامية مثل تلك المقترحة للمناقشة هنا: https://github.com/dotnet/corefx/issues/13757

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

راجع للشغل: استخدم ASP.NET النمط السلس لدمج رمز التجزئة في الأصل ، لكنه توقف عن القيام بذلك لأنه يؤدي إلى تفويت الأخطاء بسهولة: https://github.com/aspnet/Razor/pull/537

jkotas بخصوص أمان تدفق التجزئة.
إخلاء المسؤولية: لست خبيرًا (يجب عليك استشارة أحدهم ولديك أكثر من عدد قليل بخصوص هذه المشكلة) .

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

تحرير: لقد نشرت أثناء كتابتي. في ظل هذا ، ما الذي تجنيه لك دولة 64 بت؟

jkotas لقد بحثت في المشكلة التي ربطتها بها. انها تقول:

رد فعل على aspnet / Common # 40

وصف https://github.com/aspnet/Common/issues/40 :

بقعة الخلل:

public class TagBuilder
{
    private Dictionary<string, string> _attributes;
    private string _tagName;
    private string _innerContent;

    public override int GetHashCode()
    {
        var hash = HashCodeCombiner.Start()
            .Add(_tagName, StringComparer.Ordinal)
            .Add(_innerContent, StringComparer.Ordinal);

        foreach (var kvp in _attributes)
        {
            hash.Add(kvp.Key, StringComparer.Ordinal).Add(kvp.Value, StringComparer.Ordinal);
        }

        return hash.Build();
    }
}

تعال. هذه الحجة مثل القول بأن string يجب أن يكون قابلاً للتغيير لأن الناس لا يدركون أن Substring يُرجع سلسلة جديدة. الهياكل المتغيرة هي أسوأ بكثير من حيث مسكتات ؛ أعتقد أننا يجب أن نحافظ على الهيكل غير قابل للتغيير.

فيما يتعلق بأمن تدفق التجزئة.

هناك جانبان لهذا: التصميم الصحيح عن طريق البناء (هياكل بيانات قوية ، إلخ) ؛ وتخفيف المشكلات في التصميم الحالي. كلاهما مهم

karelz بخصوص تسمية المعلمة

لقد لاحظت أنك بصمت / عن طريق الصدفة غيرت اسم الوسيطة obj إلى القيمة في اقتراحك dotnet / corefx # 8034 (تعليق). لقد عدلت إلى الهدف الذي يلتقط IMO ما تحصل عليه بشكل أفضل. ترتبط قيمة الاسم بدرجة أكبر بقيمة التجزئة "int" نفسها في هذا السياق.
أنا منفتح لمزيد من المناقشة حول اسم الحجة إذا لزم الأمر ، ولكن دعونا نغيره عن قصد ونتتبع الاختلاف مقابل الاقتراح الأخير المعتمد.

أنا أفكر ، في اقتراح مستقبلي ، في إضافة واجهات برمجة التطبيقات لدمج القيم بشكل مجمّع. على سبيل المثال: CombineRange(ReadOnlySpan<T>) . إذا قمنا بتسمية هذا obj ، فسنضطر إلى تسمية المعلمة هناك objs ، الأمر الذي يبدو محرجًا للغاية. لذا يجب أن نسميها item بدلاً من ذلك ؛ في المستقبل ، يمكننا تسمية المعلمة span items . تحديث الاقتراح.

أتفق jkotas ، لكن النقطة هنا هي أننا لا

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

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

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

لنفترض أن لديك M1 و M2 ببذور عشوائية مختلفة rs1 و rs2 ....
سيصدر M1 h1 = hash('a', rs1) و h2=hash('b', rs1)
سيصدر M2 h1' = hash('a', rs2) و h2'=hash('b', rs2)
النقطة الأساسية هنا هي أن h1 و h1' سيختلفان مع احتمال 1/ (int.MaxInt-1) (إذا كان hash جيدًا بما فيه الكفاية) وهو لجميع الأغراض كما هو جيد كما سيحصل.
لذلك ، مهما كان c(x,y) الذي قررت استخدامه (إذا كان جيدًا بدرجة كافية) ، فإنه يأخذ بالفعل في الاعتبار التخفيف المدمج في المصدر.

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

تضمين التغريدة

هل سيتمكن ASP.NET Core من استبدال أداة دمج رموز التجزئة الخاصة بها - التي تحتوي على 64 بت من الحالة حاليًا - بهذا المجمع؟

قطعا؛ يستخدم نفس خوارزمية التجزئة. لقد صنعت للتو تطبيق الاختبار هذا لقياس عدد الاصطدامات وقمت بتشغيله 10 مرات. لا يوجد فرق كبير من استخدام 64 بت.

كنت أتخيل أنواع أكواد تجزئة منفصلة وكلها ذات حجم صغير (FnvHashCode ، إلخ.)

ألا يؤدي هذا إلى انفجار اندماجي؟ يجب أن يكون جزءًا من اقتراح API لتوضيح ما يؤدي إليه تصميم واجهة برمجة التطبيقات هذا.

jkotas ، لن يحدث ذلك. لن يحدد تصميم هذه الفئة التصميم لواجهات برمجة تطبيقات التجزئة المستقبلية في الحجر. يجب اعتبار هذه السيناريوهات أكثر تقدمًا ، ويجب أن يتم طرحها في اقتراح مختلف مثل dotnet / corefx # 13757 ، وسيكون لها مناقشة تصميم مختلفة. أعتقد أنه من الأهمية بمكان أن يكون لديك واجهة برمجة تطبيقات بسيطة لخوارزمية تجزئة عامة ، للمبتدئين الذين يكافحون من أجل تجاوز GetHashCode .

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

متى نحتاج إلى حالة داخلية أكثر من 32 بت؟ تحرير: إذا كان ذلك للسماح للأشخاص بتوصيل منطق التجزئة المخصص ، أعتقد (مرة أخرى) أنه ينبغي اعتباره سيناريو متقدم ومناقشته في dotnet / corefx # 13757.

أنت تستخدم Marvin32 الذي يتغير في كل مجال الآن

حسنًا ، يتم تمكين التخفيف العشوائي لرمز التجزئة للسلسلة افتراضيًا في .NET Core. لم يتم تمكينه افتراضيًا للتطبيقات المستقلة في .NET Framework الكامل بسبب التوافق ؛ يتم تمكينه فقط من خلال المراوغات (على سبيل المثال في البيئات عالية الخطورة).

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

تحتوي خوارزمية Marvin32 المستخدمة لحساب أكواد التجزئة العشوائية للسلسلة على حالة داخلية 64 بت. تم اختياره من قبل خبراء موضوع MS. أنا متأكد من أن لديهم سببًا جيدًا لاستخدام الحالة الداخلية 64 بت ، ولم يستخدموها لمجرد جعل الأمور أبطأ.

يجب أن يستمر برنامج دمج التجزئة للأغراض العامة في تطوير هذا التخفيف: يجب أن يستخدم بذرة عشوائية ورمز تجزئة قوي بما يكفي يجمع بين الخوارزمية. من الناحية المثالية ، سيستخدم نفس Marvin32 كتجزئة سلسلة عشوائية.

تحتوي خوارزمية Marvin32 المستخدمة لحساب أكواد التجزئة العشوائية للسلسلة على حالة داخلية 64 بت. تم اختياره من قبل خبراء موضوع MS. أنا متأكد من أن لديهم سببًا جيدًا لاستخدام الحالة الداخلية 64 بت ، ولم يستخدموها لمجرد جعل الأمور أبطأ.

jkotas ، مجمع كود التجزئة الذي قمت بالربط به لا يستخدم Marvin32. يستخدم نفس خوارزمية DJBx33x الساذجة المستخدمة من قبل string.GetHashCode غير العشوائية.

يجب أن يستمر برنامج دمج التجزئة للأغراض العامة في تطوير هذا التخفيف: يجب أن يستخدم بذرة عشوائية ورمز تجزئة قوي بما يكفي يجمع بين الخوارزمية. من الناحية المثالية ، سيستخدم نفس Marvin32 كتجزئة سلسلة عشوائية.

هذا النوع غير مخصص للاستخدام في الأماكن المعرضة لهجمات التجزئة DoS. يستهدف هذا الأشخاص الذين لا يعرفون أفضل لإضافة / xor ، وسيساعد في منع أشياء مثل https://github.com/dotnet/coreclr/pull/4654.

يجب أن يستمر برنامج دمج التجزئة للأغراض العامة في تطوير هذا التخفيف: يجب أن يستخدم بذرة عشوائية ورمز تجزئة قوي بما يكفي يجمع بين الخوارزمية. من الناحية المثالية ، سيستخدم نفس Marvin32 كتجزئة سلسلة عشوائية.

ثم يجب أن نتحدث مع فريق C # لهم لتنفيذ خوارزمية تجزئة ValueTuple مخففة. لأن هذا الرمز سيتم استخدامه في البيئات عالية الخطورة أيضًا. وبالطبع Tuple https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Tuple.cs#L60 أو System.Numerics.HashHelpers (تُستخدم في جميع أنحاء مكان).

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

هذا موجه للأشخاص الذين لا يعرفون أفضل لإضافة / xor

هذا هو بالضبط سبب أهمية أن تكون قوية. تكمن القيمة الأساسية لـ .NET في أنها تعالج مشاكل الأشخاص الذين لا يعرفون أفضل.

وأثناء وجودنا فيه ، دعونا لا ننسى IntPtr https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/IntPtr.cs#L119
هذا سيء بشكل خاص ، ربما يكون xor هو الأسوأ لدينا لأن bad سيتصادم مع dab .

تنفيذ خوارزمية تجزئة ValueTuple مخففة

نقطة جيدة. لست متأكدًا مما إذا كانت قيمة ValueTuple قد تم شحنها أو ما إذا كان الوقت قد حان للقيام بذلك. تم فتحه https://github.com/dotnet/corefx/issues/14046.

دعونا لا ننسى IntPtr

هذه أخطاء سابقة ... معيار إصلاحها أعلى من ذلك بكثير.

تضمين التغريدة

هذه أخطاء سابقة ... معيار إصلاحها أعلى من ذلك بكثير.

اعتقدت أن إحدى نقاط .Net Core هي أن شريط التغييرات "الصغيرة" من هذا القبيل يجب أن يكون أقل من ذلك بكثير. إذا كان شخص ما يعتمد على تنفيذ IntPtr.GetHashCode (وهو ما لا يجب عليهم فعلاً) ، فيمكنهم اختيار عدم ترقية إصدارهم من .Net Core.

يجب أن يكون شريط التغييرات "الصغيرة" من هذا القبيل أقل من ذلك بكثير

نعم ، إنه كذلك - بالمقارنة مع .NET Framework الكامل. ولكن لا يزال يتعين عليك القيام بالعمل لدفع التغيير عبر النظام وقد تجد أنه لا يستحق العناء. المثال الأخير هو التغيير في خوارزمية التجزئة Tuple<T> التي تمت إعادتها بسبب تعطلها F #: https://github.com/dotnet/coreclr/pull/6767#issuecomment -256896016

تضمين التغريدة

إذا أردنا أن نجني HashCode 64 بت ، فهل تعتقد أن التصميم غير القابل للتغيير سيقضي على الأداء في بيئات 32 بت؟ أتفق مع القراء الآخرين ، يبدو أن نمط البناء أسوأ بكثير.

اقتل الأداء - لا. دفع غرامة الأداء لسكر النحو - نعم.

دفع غرامة الأداء لسكر النحو - نعم.

هل هو شيء يمكن تحسينه بواسطة JIT في المستقبل؟

يقتل الكمال - لا.
دفع غرامة الأداء لسكر النحو - نعم.

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

نقلا عنك من قبل:

هذا هو بالضبط سبب أهمية أن تكون قوية. تكمن القيمة الأساسية لـ .NET في أنها تعالج مشاكل الأشخاص الذين لا يعرفون أفضل.

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

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

يوافق. الفكر ، أعتقد أنه من المؤسف أن هذا هو الحال بالنسبة لأنواع منشئ الهياكل القابلة للتغيير. أنا استخدم لهم كل و الوقت بسبب أنها جميلة وضيق. التعليقات التوضيحية [MustNotCopy] أي شخص؟

MustNotCopy هو حلم عاشق الهيكل أصبح حقيقة. @ جاريدبار؟

MustNotCopy مثل المكدس فقط ولكن يصعب استخدامه 😄

أقترح عدم إنشاء أي فئة ولكن إنشاء طرق امتداد لدمج التجزئة

static class HashHelpers
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int CombineHash(this int hash1, int hash2);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int CombineHash<T>(this int hash, T value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int CombineHash<T>(this int hash, T value, IEqualityComparer<T> comparer);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int CombineHash<T>(this int hash, IEnumerable<T> values);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int CombineHash<T>(this int hash, IEnumerable<T> values, IEqualityComparer<T> comparer);
}

هذا كل شيء! إنه سريع وسهل الاستخدام.

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

أيضًا ، لديك طرق تواصل سلسلة من حساب كود التجزئة ، ولكن كيف تبدأها؟ هل عليك أن تفعل شيئًا غير واضح مثل البدء بصفر؟ أي 0.CombineHash(this.FirstName).CombineHash(this.LastName) .

تحديث: حسب التعليق في dotnet / corefx # 14046 ، تقرر الاحتفاظ بصيغة التجزئة الحالية مقابل ValueTuple :

jamesqo شكرا للمساعدة.
من المناقشة الأخيرة مع jkotas و VSadov ، نحن بخير للمضي قدمًا في التوزيع العشوائي / البذر ، لكننا نفضل التراجع عن اعتماد دالة تجزئة أكثر تكلفة.
يؤدي إجراء العشوائية إلى إبقاء الباب لتغيير وظيفة التجزئة في المستقبل إذا دعت الحاجة.

jkotas ، هل يمكننا الاحتفاظ HashCode ثم تقليصها إلى 4 بايت؟ هذا من شأنه القضاء على جميع مشاكل نسخ البنية. يمكن أن يكون لدينا HashCode.Empty يمثل قيمة تجزئة عشوائية.

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

0.CombineHash(this.FirstName).CombineHash(this.LastName) كـ this.FirstName.GetHash().CombineHash(this.LastName)

للتنفيذ بدءًا من البذور ، يمكن أن يكون له الطريقة الثابتة التالية

static class HashHelpers
{
    public static int ClassSeed<T>();
}

class SomeClass
{
    int GetHash()
    {
        return HashHelpers.ClassSeed<SomeClass>().CombineHash(value1).CombineHash(value2);
    }
}

لذلك سيكون لكل فئة بذرة مختلفة لعشوائية التجزئة.

jkotas ، هل يمكننا الاحتفاظ

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

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

jkotas الذي يحتوي على حالة الدمج نفسها لاستخدام حالة

تعرف على ما يجعل وظيفة التجزئة جيدة (والتي من الواضح أن بعضها يشبه xor : http://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and -السرعة و https://research.neustar.biz/2012/02/02/choosing-a-good-hash-function-part-3/

jamesqo راجع للشغل ، أدركت للتو أن أداة التجميع لن تعمل في حالة: "أنا في الواقع أقوم بدمج التجزئة (وليس تجزئات وقت التشغيل) لأن البذرة ستتغير في كل مرة." ... منشئ عام بالبذور؟

تضمين التغريدة

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

هل هذا مهم عندما ينتهي الأمر بالتكثيف إلى int في النهاية؟

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

نتيجة طبيعية: إذا كنت تتعاون ، تأكد من أن الوظيفة ممتازة إحصائيًا أو يكاد يكون من المؤكد أنك ستجعلها أسوأ.

هذا يعتمد على ما إذا كان هناك ارتباط بين العناصر. إذا لم يكن هناك ارتباط ، فإن حالة 32 بت و rotl البسيط (أو حتى xor) تعمل بشكل جيد. إذا كان هناك ارتباط ، فهذا يعتمد.

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

for (int i = 0; i < str.Length; i++)
   hashCodeBuilder.Add(str[i]);

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

سيكون من الرائع إضافة الطريقة التالية إلى واجهة برمجة التطبيقات التي تدعم عشوائية التجزئة.

namespace System
{
    public struct HashCode : IEquatable<HashCode>
    {
       // add this
       public static HashCode CreateRandomized(Type type);
       // or add this
       public static HashCode CreateRandomized<T>();
    }
}

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

إذا كنا نرغب في ألا يقوم أحد بأشياء غبية به ، فإن استخدام حالة 64 بت لا يصلح أي شيء ، فنحن فقط نخفي المشكلة. سيظل من الممكن إنشاء إدخال يستغل هذا الارتباط. وهو ما يوجهنا مرة أخرى إلى نفس الحجة التي قدمتها قبل 18 يومًا. انظر: https://github.com/dotnet/corefx/issues/8034#issuecomment -261301533

أنا أفضل التصميم لمرة واحدة أن هذه ليست وظيفة تجزئة غير مشفرة ، ولكنها طريقة سريعة للجمع بين أكواد التجزئة غير المرتبطة

أسرع طريقة للجمع بين أكواد التجزئة غير المرتبطة هي xor ...

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

أضف التوزيع العشوائي لرمز التجزئة باستخدام public static HashCode CreateRandomized(Type type); أو باستخدام طرق public static HashCode CreateRandomized<T>(); أو بكليهما.

jkotas أعتقد أنني قد وجدت نمطًا أفضل لهذا. ماذا لو استخدمنا إرجاع المرجع C # 7؟ بدلاً من إرجاع HashCode كل مرة ، سنقوم بإرجاع ref HashCode الذي يتناسب مع السجل.

public struct HashCode
{
    private readonly long _value;

    public ref HashCode Combine(int hashCode)
    {
        CombineCore(ref _value, hashCode); // note: modifies the struct in-place
        return ref this;
    }
}

يظل الاستخدام كما كان من قبل:

return HashCode.Combine(1)
    .Combine(2).Combine(3);

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

( ref this لا يعمل بعد ، لكني أرى العلاقات العامة في Roslyn لتمكينه هنا. )


AlexRadch لا أعتقد أنه من الحكمة دمج التجزئة أكثر مع النوع ، لأن الحصول على كود التجزئة من النوع مكلف.

jamesqo public static HashCode CreateRandomized<T>(); لا تحصل على كود التجزئة من النوع. يقوم بإنشاء HashCode عشوائي لهذا النوع.

jamesqo " ref this لا يعمل بعد". حتى بمجرد إصلاح مشكلة Roslyn ، لن يكون ref this متاحًا لـ corefx repo لفترة من الوقت (لست متأكدًا من المدة ، ربما يستطيع stephentoub تحديد التوقعات).

مناقشة التصميم لا تتقارب هنا. علاوة على ذلك ، من الصعب جدًا متابعة التعليقات الـ 200.
نخطط للحصول على jkotas الأسبوع المقبل وطرد الاقتراح في مراجعة API الثلاثاء المقبل. سنقوم بعد ذلك بنشر الاقتراح مرة أخرى هنا لمزيد من التعليقات.

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

jcouv أنا موافق على أنه لا يعمل بعد ، طالما أنه يمكننا متابعة هذا التصميم عند إصداره. (أعتقد أيضًا أنه قد يكون من الممكن حل هذه المشكلة مؤقتًا باستخدام Unsafe .)

@ Karelz موافق: ابتسم: وسأفتح اقتراحًا جديدًا. أنا موافق؛ لا يستطيع متصفحي التعامل مع أكثر من 200 تعليق بهذه الجودة.

@ karelz أصبت بعقبة ؛ اتضح أن العلاقات العامة المعنية كانت تحاول تمكين عوائد ref this لأنواع المراجع بدلاً من أنواع القيم. لا يمكن إرجاع ref this بأمان من الهياكل ؛ انظر هنا لماذا. لذلك لن تعمل تسوية إعادة المرجع.

على أي حال ، سأغلق هذا الموضوع. لقد فتحت مشكلة أخرى هنا: https://github.com/dotnet/corefx/issues/14354

يجب أن تكون قادرًا على إرجاع المرجع "هذا" من منشور طريقة امتداد نوع القيمة https://github.com/dotnet/roslyn/pull/15650 على الرغم من أنني أفترض C # vNext ...

تضمين التغريدة

يجب أن تكون قادرًا على إرجاع المرجع "this" من طريقة امتداد نوع القيمة post dotnet / roslyn # 15650 على الرغم من أنني أفترض C # vNext ...

صيح. من الممكن إرجاع this من طريقة امتداد ref this . لا يمكن إرجاع this من طريقة مثيل بنية عادية. هناك الكثير من تفاصيل الحياة الدموية عن سبب حدوث ذلك:

تضمين التغريدة

إذا أردنا أن نكون صارمين ، يجب أن تكون التجزئة الوحيدة هي uint ، فيمكن اعتبارها سهوًا أن إطار العمل يُرجع int تحت هذا الضوء.

CLS- الامتثال؟ الأعداد الصحيحة غير الموقعة ليست متوافقة مع CLS.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات