Runtime: Menambahkan jenis HashCode untuk membantu menggabungkan kode hash

Dibuat pada 25 Apr 2016  ·  206Komentar  ·  Sumber: dotnet/runtime

Mengganti diskusi panjang dengan 200+ komentar dengan edisi baru dotnet/corefx#14354

Masalah ini DITUTUP!!!


Motivasi

Java memiliki Objects.hash untuk dengan cepat menggabungkan kode hash bidang konstituen untuk kembali dalam Object.hashCode() . Sayangnya, .NET tidak memiliki padanan seperti itu dan pengembang terpaksa menggulung hash mereka sendiri seperti ini :

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

Kadang-kadang orang bahkan menggunakan Tuple.Create(field1, field2, ...).GetHashCode() untuk ini, yang buruk (jelas) karena mengalokasikan.

Usul

  • Daftar perubahan dalam proposal saat ini (terhadap versi terakhir yang disetujui https://github.com/dotnet/corefx/issues/8034#issuecomment-262331783):

    • Empty properti ditambahkan (sebagai titik awal alami yang analog dengan ImmutableArray )

    • Nama argumen diperbarui: 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();
    }
}

Penggunaan:

```c#
int kode hash1 = Kode Hash.Buat(f1).Gabungkan(f2).Nilai;
int hashCode2 = hash.Aggregate(HashCode.Empty, (seed, hash) => seed.Combine(hash));

var hashCode3 = HashCode.Empty;
foreach (int hash dalam hash) { hashCode3 = hashCode3.Combine(hash); }
(int)hashCode3;
```

Catatan

Implementasi harus menggunakan algoritma dalam HashHelpers .

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

Komentar yang paling membantu

[@redknightlois] Jika yang kita butuhkan adalah alasan mengapa harus pergi untuk System Saya dapat mencoba pembenaran. Kami membangun HashCode untuk membantu implementasi object.GetHashCode() , kedengarannya cocok bahwa keduanya akan berbagi namespace.

Itulah alasan yang saya dan @KrzysztofCwalina gunakan juga. Terjual!

Semua 206 komentar

Jika Anda menginginkan sesuatu yang cepat dan kotor maka Anda dapat menggunakan ValueTuple.Create(field1, field2).GetHashCode() . Ini adalah algoritma yang sama seperti yang digunakan dalam Tuple (yang dalam hal ini, mirip dengan yang di Objects ) dan tidak memiliki alokasi overhead.

Kalau tidak, ada pertanyaan tentang seberapa baik hash yang Anda perlukan, kemungkinan nilai bidang apa yang akan ada (yang memengaruhi algoritma mana yang akan memberikan hasil yang baik atau buruk), apakah ada kemungkinan serangan hashDoS, lakukan tabrakan modulo biner- nomor genap terluka (seperti yang mereka lakukan dengan tabel hash biner-genap), dan seterusnya, membuat satu-untuk-semua tidak dapat diterapkan.

@JonHanna Saya pikir pertanyaan itu juga berlaku untuk, misalnya, string.GetHashCode() . Saya tidak mengerti mengapa memberikan Hash harus lebih sulit dari itu.

Sebenarnya, seharusnya lebih sederhana, karena pengguna dengan persyaratan khusus dapat dengan mudah berhenti menggunakan Hash , tetapi berhenti menggunakan string.GetHashCode() lebih sulit.

Jika Anda menginginkan sesuatu yang cepat dan kotor maka Anda dapat menggunakan ValueTuple.Create(field1, field2).GetHashCode().

Ah, ide bagus, saya tidak memikirkan ValueTuple saat membuat posting ini. Sayangnya saya tidak berpikir itu akan tersedia sampai C# 7/rilis kerangka kerja berikutnya, atau bahkan tahu apakah itu akan berkinerja seperti itu ( pemanggilan properti/metode ke EqualityComparer dapat bertambah). Tetapi saya belum mengambil tolok ukur untuk mengukur ini, jadi saya tidak akan benar-benar tahu. Saya hanya berpikir bahwa harus ada kelas khusus/sederhana untuk hashing yang dapat digunakan orang tanpa menggunakan tupel sebagai solusi peretasan.

Kalau tidak, ada pertanyaan tentang seberapa baik hash yang Anda perlukan, kemungkinan nilai bidang apa yang akan ada (yang memengaruhi algoritma mana yang akan memberikan hasil yang baik atau buruk), apakah ada kemungkinan serangan hashDoS, lakukan tabrakan modulo biner- nomor genap terluka (seperti yang mereka lakukan dengan tabel hash biner-genap), dan seterusnya, membuat satu-untuk-semua tidak dapat diterapkan.

Benar-benar setuju, tetapi saya tidak berpikir sebagian besar implementasi memperhitungkannya, misalnya implementasi ArraySegment saat ini cukup naif. Tujuan utama kelas ini (bersama dengan menghindari alokasi) adalah untuk menyediakan implementasi masuk bagi orang-orang yang tidak tahu banyak tentang hashing, untuk mencegah mereka melakukan sesuatu yang bodoh seperti ini . Orang yang perlu menangani situasi yang Anda gambarkan dapat mengimplementasikan algoritma hashing mereka sendiri.

Sayangnya saya tidak berpikir itu akan tersedia sampai C# 7/rilis kerangka kerja berikutnya

Saya pikir Anda dapat menggunakannya dengan C# 2, hanya saja tidak dengan dukungan bawaan.

atau bahkan tahu apakah itu yang berkinerja seperti itu (pemanggilan properti/metode itu ke EqualityComparer dapat bertambah)

Apa yang akan dilakukan kelas ini secara berbeda? Jika secara eksplisit memanggil obj == null ? 0 : obj.GetHashCode() lebih cepat, dari itu harus dipindahkan ke ValueTuple .

Saya cenderung memberi +1 proposal ini beberapa minggu yang lalu, tetapi saya kurang cenderung mengingat ValueTuple mengurangi alokasi overhead dari trik menggunakan Tuple untuk ini, ini tampaknya berada di antara dua bangku bagi saya: Jika Anda tidak memerlukan sesuatu yang sangat khusus maka Anda dapat menggunakan ValueTuple , tetapi jika Anda membutuhkan sesuatu di luar itu, maka kelas seperti ini tidak akan berjalan jauh cukup.

Dan ketika kita memiliki C#7, itu akan memiliki gula sintaksis untuk membuatnya lebih mudah.

@JonHanna

Apa yang akan dilakukan kelas ini secara berbeda? Jika secara eksplisit memanggil obj == null ? 0 : obj.GetHashCode() lebih cepat, daripada yang harus dipindahkan ke ValueTuple.

Mengapa tidak memiliki ValueTuple cukup gunakan kelas Hash untuk mendapatkan kode hash? Itu juga akan mengurangi LOC dalam file secara signifikan (yang saat ini sekitar ~2000 baris).

edit:

Jika Anda tidak memerlukan sesuatu yang khusus, Anda dapat menggunakan ValueTuple

Benar, tetapi masalahnya adalah banyak orang mungkin tidak menyadarinya dan menerapkan fungsi hashing naif yang lebih rendah (seperti yang saya tautkan di atas).

Bahwa aku memang bisa berada di belakang.

Mungkin di luar cakupan masalah ini. Tetapi memiliki ruang nama hashing di mana kita dapat menemukan hash kriptografi dan non-kriptografi berkinerja tinggi yang ditulis oleh para ahli akan menjadi kemenangan di sini.

Misalnya, kami harus membuat kode xxHash32, xxHash64, Metro128 dan juga downsampling dari 128 ke 64 dan dari 64 ke 32 bit sendiri. Memiliki serangkaian fungsi yang dioptimalkan dapat membantu pengembang untuk menghindari penulisan yang tidak dioptimalkan dan/atau buggy mereka sendiri (saya tahu, kami juga menemukan beberapa bug di dalam kami sendiri); tapi tetap bisa memilih tergantung kebutuhan.

Kami dengan senang hati akan menyumbangkan implementasi kami jika ada minat, sehingga dapat ditinjau dan dioptimalkan lebih lanjut oleh para ahli.

@redknightlois Saya akan dengan senang hati menambahkan implementasi SpookyHash saya ke upaya seperti itu.

@svick Hati-hati dengan string.GetHashCode() meskipun, itu sangat spesifik, untuk alasan yang sangat bagus, serangan Hash DoS.

@terrajobst , seberapa jauh ini dalam antrian triase/ulasan API keluar? Saya pikir ini adalah API sederhana yang selalu ingin kami tambahkan ke platform dan mungkin kami sekarang memiliki cukup massa kritis untuk benar-benar melakukannya?

cc: @ellismg

Saya pikir itu siap untuk ditinjau dalam kondisi saat ini.

@mellinoe Itu bagus! Saya telah membersihkan proposal sedikit untuk membuatnya lebih singkat, dan juga menambahkan beberapa pertanyaan di bagian akhir yang menurut saya harus dijawab.

@jamesqo Harus long juga.

@redknightlois , terdengar masuk akal. Saya memperbarui proposal untuk memasukkan long kelebihan Combine .

Apakah saran @JonHanna tidak cukup baik?

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

Kecuali ada alasan yang cukup baik mengapa itu tidak cukup baik, kami tidak berpikir itu akan berhasil.

Di luar kode yang dihasilkan menjadi beberapa kali lipat lebih buruk, saya tidak dapat memikirkan alasan lain yang cukup baik. Kecuali tentu saja ada pengoptimalan dalam runtime baru yang menangani kasus khusus ini, dalam hal ini analisis ini dapat diperdebatkan. Karena itu saya mencoba ini pada 1.0.1.

Mari saya ilustrasikan dengan sebuah contoh.

Misalkan kita mengambil kode aktual yang digunakan untuk ValueTuple dan menggunakan konstanta untuk memanggilnya.

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

Sekarang di bawah kemungkinan kompiler yang mengoptimalkan adalah bahwa seharusnya tidak ada perbedaan, tetapi pada kenyataannya ada.

Ini adalah kode sebenarnya untuk ValueTuple

image
Jadi sekarang apa yang bisa dilihat di sini? Pertama kita membuat struct di stack, lalu kita memanggil kode hash yang sebenarnya.

Sekarang bandingkan dengan penggunaan HashHelper.Combine yang untuk semua tujuan itu bisa menjadi implementasi aktual dari Hash.Combine

image

Aku tahu!!!
Tapi jangan berhenti di situ... mari gunakan parameter aktual:

        [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

Hal yang baik, ini sangat stabil. Tapi mari kita bandingkan dengan alternatifnya:

image

Sekarang mari kita berlebihan...

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

Dan hasilnya cukup ilustratif

image

Saya tidak dapat benar-benar memeriksa kode aktual yang dihasilkan JIT untuk panggilan tersebut, tetapi hanya prolog dan epilog sudah cukup untuk membenarkan penyertaan proposal.

image

Kesimpulan dari analisisnya sederhana: bahwa tipe holding adalah struct tidak berarti gratis :)

Kinerja diangkat selama pertemuan. Pertanyaannya adalah apakah API ini kemungkinan akan berada di jalur panas. Untuk lebih jelasnya, saya tidak mengatakan kita seharusnya tidak memiliki API. Saya hanya mengatakan kecuali ada skenario konkret, lebih sulit untuk merancang API karena kami tidak dapat mengatakan "kami membutuhkannya untuk X, jadi ukuran keberhasilannya adalah apakah X dapat menggunakannya". Itu penting untuk API yang tidak memungkinkan Anda melakukan sesuatu yang baru, melainkan melakukan hal yang sama dengan cara yang lebih optimal.

Saya pikir semakin penting untuk memiliki hash yang cepat dan berkualitas baik, semakin penting untuk menyetel algoritme yang digunakan untuk objek dan rentang nilai yang mungkin terlihat, dan karenanya semakin Anda membutuhkannya pembantu, semakin Anda perlu untuk tidak menggunakan pembantu seperti itu.

@terrajobst , kinerja adalah motivasi utama untuk proposal ini tetapi bukan satu-satunya. Memiliki tipe khusus akan membantu kemampuan untuk ditemukan; bahkan dengan dukungan tuple bawaan di C# 7, pengembang mungkin belum tentu tahu bahwa mereka disamakan dengan nilai. Bahkan jika mereka melakukannya, mereka mungkin lupa bahwa tupel menimpa GetHashCode , dan kemungkinan besar akan berakhir di Google bagaimana menerapkan GetHashCode di .NET.

Juga, ada masalah kebenaran halus dengan menggunakan ValueTuple.Create.GetHashCode . 8 elemen terakhir, hanya 8 elemen terakhir yang di-hash; sisanya diabaikan.

@terrajobst Di RavenDB GetHashCode, kinerja sangat sukses di bottomline kami sehingga kami akhirnya menerapkan seluruh rangkaian rutinitas yang sangat dioptimalkan. Bahkan Roslyn memiliki hashing internal mereka sendiri https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/InternalUtilities/Hash.cs juga memeriksa diskusi tentang Roslyn secara khusus di sini: https://github .com/dotnet/coreclr/issues/1619 ... Jadi ketika kinerja adalah KUNCI, kami tidak dapat menggunakan platform yang disediakan dan harus menggulung sendiri (dan membayar konsekuensinya).

Juga masalah @jamesqo sepenuhnya valid. Tidak perlu menggabungkan begitu banyak hash, tetapi untuk kasus 1M ada seseorang yang akan melangkahi tebing dengan yang itu.

@JonHanna

Saya pikir semakin penting untuk memiliki hash yang cepat dan berkualitas baik, semakin penting untuk menyetel algoritme yang digunakan untuk objek dan rentang nilai yang mungkin terlihat, dan karenanya semakin Anda membutuhkannya pembantu, semakin Anda perlu untuk tidak menggunakan pembantu seperti itu.

Jadi Anda mengatakan bahwa menambahkan kelas pembantu akan buruk, karena itu akan mendorong orang untuk hanya memasukkan fungsi pembantu tanpa memikirkan bagaimana melakukan hash yang tepat?

Sepertinya kebalikannya akan benar, sebenarnya; Hash.Combine secara umum harus meningkatkan implementasi GetHashCode . Orang yang tahu cara melakukan hashing dapat mengevaluasi Hash.Combine untuk melihat apakah cocok dengan kasus penggunaan mereka. Pemula yang tidak benar-benar tahu tentang hashing akan menggunakan Hash.Combine bukan hanya xor-ing (atau lebih buruk, menambahkan) bidang konstituen karena mereka tidak tahu bagaimana melakukan hash yang benar.

Kami membahas ini sedikit lebih banyak dan Anda meyakinkan kami :-)

Beberapa pertanyaan lagi:

  1. Kita perlu memutuskan di mana harus meletakkan jenis ini. Memperkenalkan namespace baru tampaknya aneh; System.Numerics mungkin berhasil. System.Collections.Generic mungkin juga berfungsi, karena memiliki pembanding dan hashing paling sering digunakan dalam konteks koleksi.
  2. Haruskah kita menyediakan pola pembangun bebas alokasi untuk menggabungkan sejumlah kode hash yang tidak diketahui?

Pada (2) @Eilon mengatakan ini:

Untuk referensi, ASP.NET Core (dan pendahulunya serta proyek terkait) menggunakan HashCodeCombiner: https://github.com/aspnet/Common/blob/dev/src/Microsoft.Extensions.HashCodeCombiner.Sources/HashCodeCombiner.cs

( @David Fowler menyebutkannya di utas GitHub beberapa bulan yang lalu.)

Dan ini adalah contoh penggunaan: https://github.com/aspnet/Mvc/blob/760c8f38678118734399c58c2dac981ea6e47046/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs#L129 -L144

``` C#
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(IsMainPage ? 1 : 0);
hashCodeCombiner.Add(ViewName, StringComparer.Ordinal);
hashCodeCombiner.Add(ControllerName, StringComparer.Ordinal);
hashCodeCombiner.Add(NamaArea, StringComparer.Ordinal);

jika (ViewLocationExpanderValues ​​!= null)
{
foreach (item var di ViewLocationExpanderValues)
{
hashCodeCombiner.Add(item.Key, StringComparer.Ordinal);
hashCodeCombiner.Add(item.Value, StringComparer.Ordinal);
}
}

kembali kode hashCombiner;
```

Kami membahas ini sedikit lebih banyak dan Anda meyakinkan kami :-)

🎉

Memperkenalkan namespace baru tampaknya aneh; System.Numerics mungkin berfungsi.

Jika kita memutuskan untuk tidak menambahkan namespace baru, maka perlu dicatat bahwa kode apa pun yang memiliki kelas bernama Hash dan direktif using System.Numerics akan gagal dikompilasi dengan kesalahan tipe yang ambigu.

Haruskah kita menyediakan pola pembangun bebas alokasi untuk menggabungkan sejumlah kode hash yang tidak diketahui?

Ini terdengar seperti ide yang bagus. Sebagai beberapa saran awal, mungkin kita harus menamainya HashBuilder (ala StringBuilder ) dan memberinya return this setelah setiap metode Add untuk membuatnya lebih mudah untuk menambahkan hash, seperti:

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

@jamesqo tolong perbarui proposal di atas ketika ada konsensus di utas. Kami kemudian dapat melakukan tinjauan akhir. Menugaskan kepada Anda untuk saat ini saat Anda menjalankan desain ;-)

Jika kita memutuskan untuk tidak menambahkan namespace baru, maka perlu dicatat bahwa kode apa pun yang memiliki kelas bernama Hash dan direktif using System.Numerics akan gagal dikompilasi dengan kesalahan tipe yang ambigu.

Tergantung pada skenario yang sebenarnya. Dalam banyak kasus, kompiler akan lebih memilih tipe Anda karena hierarki namespace yang ditentukan dari unit kompilasi berjalan sebelum mempertimbangkan untuk menggunakan arahan.

Namun demikian: menambahkan API bisa menjadi perubahan yang merusak sumber. Namun, tidak praktis untuk menghindari hal ini, dengan asumsi kita ingin membuat kemajuan ke depan Kita biasanya berusaha untuk menghindari konflik dengan, misalnya, menggunakan nama yang tidak terlalu umum. Misalnya, saya tidak berpikir kita harus memanggil tipe Hash . Saya pikir HashCode mungkin akan lebih baik.

Sebagai beberapa saran awal, mungkin kita harus menamakannya HashBuilder

Sebagai perkiraan pertama saya berpikir untuk menggabungkan statika dan pembangun menjadi satu tipe, seperti:

``` C#
namespace System.Collections.Generic
{
struct publik HashCode
{
Gabungkan int statis publik (int hash1, int hash2);
Gabungkan int statis publik (int hash1, int hash2, int hash3);
Gabungkan int statis publik (int hash1, int hash2, int hash3, int hash4);
Gabungkan int statis publik (int hash1, int hash2, int hash3, int hash4, int hash5);
public static int Combine(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);

sebaik:

``` C#
var kode hash = kode hash baru();
hashCode.Combine(IsMainPage ? 1 : 0);
hashCode.Combine(ViewName, StringComparer.Ordinal);
hashCode.Combine(Nama Pengontrol, StringComparer.Ordinal);
kode hash.Combine(NamaArea, StringComparer.Ordinal);

jika (ViewLocationExpanderValues ​​!= null)
{
foreach (item var di ViewLocationExpanderValues)
{
kode hash.Combine(item.Key, StringComparer.Ordinal);
kode hash.Combine(item.Value, StringComparer.Ordinal);
}
}

kembali kode hash.Nilai;
```

Pikiran?

Saya suka ide @jamesqo tentang panggilan berantai (kembalikan this dari metode instan Combine ).

Saya bahkan akan menghapus metode statis sepenuhnya dan hanya menyimpan metode instan ...

Combine(long hashCode) hanya akan diturunkan ke int . Apakah kita benar-benar menginginkan itu?
Apa kasus penggunaan untuk kelebihan long ?

@karelz Tolong jangan hapus, struct tidak gratis. Hash dapat digunakan di jalur yang sangat panas, Anda tentu tidak ingin menyia-nyiakan instruksi ketika metode statis pada dasarnya gratis. Lihatlah analisis kode di mana saya menunjukkan dampak nyata dari struct terlampir.

Kami menggunakan kelas statis Hashing untuk menghindari bentrokan nama dan kode terlihat bagus.

@redknightlois Saya ingin tahu apakah kita harus mengharapkan kode 'buruk' yang sama juga dalam kasus struct non-generik dengan satu bidang int.
Jika itu masih kode perakitan 'buruk', saya ingin tahu apakah kami dapat meningkatkan JIT untuk melakukan pekerjaan yang lebih baik dalam pengoptimalan di sini. Menambahkan API hanya untuk menyimpan beberapa instruksi harus menjadi pilihan terakhir kami IMO.

@redknightlois Penasaran, apakah JIT menghasilkan kode yang lebih buruk jika struct (dalam hal ini HashCode ) dapat masuk ke dalam register? Itu hanya akan menjadi int besar.

Juga, saya telah melihat banyak permintaan tarik di coreclr baru-baru ini untuk meningkatkan kode yang dihasilkan di sekitar struct, dan sepertinya dotnet/coreclr#8057 akan mengaktifkan pengoptimalan tersebut. Mungkin kode yang dihasilkan JIT akan lebih baik setelah perubahan ini?

edit: Saya melihat @karelz telah menyebutkan poin saya di sini.

@karelz , saya setuju dengan Anda-- dengan asumsi JIT menghasilkan kode yang layak untuk struct berukuran int (yang saya yakini, ImmutableArray tidak memiliki overhead misalnya) maka kelebihan statis adalah berlebihan dan dapat dihilangkan.

@terrajobst Beberapa ide lagi yang saya miliki:

  • Saya pikir kita bisa sedikit menggabungkan ide Anda & saya. HashCode sepertinya nama yang bagus; itu tidak harus berupa struct yang bisa berubah mengikuti pola builder. Sebagai gantinya, ini bisa menjadi pembungkus yang tidak dapat diubah di sekitar int , dan setiap operasi Combine dapat mengembalikan nilai HashCode . Sebagai contoh
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);
  • Kita seharusnya hanya memiliki operator implisit untuk konversi ke int sehingga orang tidak perlu melakukan panggilan .Value .
  • Re Combine , apakah itu nama yang terbaik? Kedengarannya lebih deskriptif, tetapi Add lebih pendek & lebih mudah untuk diketik. ( Mix adalah alternatif lain, tetapi itu sedikit menyakitkan untuk diketik.)

    • public void Combine(string text, StringComparison comparison) : Saya tidak berpikir itu benar-benar termasuk dalam tipe yang sama, karena ini tidak terkait dengan string. Selain itu, cukup mudah untuk menulis StringComparer.XXX.GetHashCode(str) untuk saat-saat langka yang Anda perlukan.

    • Kita harus menghapus kelebihan panjang dari tipe ini & memiliki tipe HashCode untuk long. Sesuatu seperti Int64HashCode , atau LongHashCode .

Saya membuat contoh kecil implementasi hal-hal di TryRoslyn: http://tinyurl.com/zej9yux

Untungnya mudah untuk memeriksanya. Dan kabar baiknya adalah itu berfungsi dengan baik apa adanya 👍

image

Kita seharusnya hanya memiliki operator implisit untuk konversi ke int sehingga orang tidak harus memiliki panggilan .Value terakhir itu.

Mungkin, kodenya tidak sesederhana itu, memiliki konversi implisit akan sedikit membersihkannya. Saya masih menyukai gagasan untuk dapat memiliki beberapa antarmuka parameter juga.

        [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, apakah itu nama yang terbaik? Kedengarannya lebih deskriptif, tetapi Add lebih pendek & lebih mudah untuk diketik. (Mix adalah alternatif lain, tetapi mengetiknya agak menyakitkan.)

Combine adalah nama sebenarnya yang digunakan dalam komunitas hashing afaik. Dan itu memberi Anda gambaran yang jelas tentang apa yang sedang dilakukannya.

@jamesqo Ada banyak fungsi hashing, kami harus mengimplementasikan versi yang sangat cepat, dari 32bit, 64bit hingga 128bit untuk RavenDB (dan kami menggunakan setiap versi untuk tujuan yang berbeda).

Kami dapat berpikir maju dalam desain ini dengan beberapa mekanisme yang dapat diperluas seperti ini:

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

Saya tidak tahu mengapa JIT membuat kode prolog yang sangat mengganggu untuk ini. Seharusnya tidak jadi mungkin bisa dioptimalkan, kita harus meminta pengembang JIT untuk itu. Tetapi untuk sisanya, Anda dapat menerapkan Penggabung yang berbeda sebanyak yang Anda inginkan tanpa membuang satu instruksi pun. Karena itu, metode ini mungkin lebih berguna untuk fungsi hash yang sebenarnya daripada untuk penggabung. cc @CarolEidt @AndyAyersMS

EDIT: Berpikir keras di sini untuk mekanisme umum untuk menggabungkan fungsi hash crypto dan non-crypto di bawah payung konsep hashing tunggal.

@jamesqo

itu tidak harus berupa struct yang bisa berubah mengikuti pola builder

Ah iya. Dalam hal ini saya baik-baik saja dengan pola itu. Saya biasanya tidak menyukai pola pengembalian instance jika operasi memiliki efek samping. Ini sangat buruk jika API mengikuti pola WithXxx tidak dapat diubah. Namun dalam kasus ini, polanya pada dasarnya adalah struktur data yang tidak dapat diubah sehingga polanya akan berfungsi dengan baik.

Saya pikir kita bisa sedikit menggabungkan ide Anda & saya.

👍, jadi bagaimana dengan:

``` C#
struct publik HashCode
{
Buat HashCode statis publik(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);
}

serta ini:

``` C#
var kode hash = kode hash baru ()
.Combine(IsMainPage ? 1: 0)
.Combine(ViewName, StringComparer.Ordinal)
.Combine(ControllerName, StringComparer.Ordinal)
.Combine(NamaArea, StringComparer.Ordinal);

jika (ViewLocationExpanderValues ​​!= null)
{
foreach (item var di ViewLocationExpanderValues)
{
kode hash = kode hash.Combine(item.Key, StringComparer.Ordinal);
hashCode = hashCode.Combine(item.Value, StringComparer.Ordinal);
}
}

kembali kode hash.Nilai;
```

@terrajobst Pikiran:

  1. Metode pabrik Create<T> harus dihapus. Jika tidak, akan ada 2 cara untuk menulis hal yang sama, HashCode.Create(_val) atau new HashCode().Combine(_val) . Juga, memiliki nama yang berbeda untuk Create / Combine tidak akan ramah perbedaan karena jika Anda menambahkan bidang pertama yang baru, Anda harus mengubah 2 baris.
  2. Saya tidak berpikir kelebihan menerima string/StringComparison termasuk di sini; HashCode tidak ada hubungannya dengan string. Sebagai gantinya, mungkin kita harus menambahkan api GetHashCode(StringComparison) ke string? (Juga semuanya adalah perbandingan ordinal, yang merupakan perilaku default string.GetHashCode .)
  3. Apa gunanya memiliki Value , jika sudah ada operator implisit untuk konversi ke int ? Sekali lagi, ini akan menyebabkan orang yang berbeda menulis hal yang berbeda.
  4. Kita harus memindahkan kelebihan long ke tipe baru. HashCode hanya akan memiliki lebar 32 bit; tidak bisa muat lama.
  5. Mari kita tambahkan beberapa kelebihan dengan mengambil tipe yang tidak ditandatangani, karena mereka lebih umum di hashing.

Inilah API yang saya usulkan:

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

Dengan hanya metode ini, contoh dari ASP.NET masih dapat ditulis sebagai:

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;

@jamesqo

Apa gunanya memiliki Value , jika sudah ada operator implisit untuk konversi ke int ? Sekali lagi, ini akan menyebabkan orang yang berbeda menulis hal yang berbeda.

Pedoman Desain Kerangka Kerja untuk kelebihan operator mengatakan:

PERTIMBANGKAN menyediakan metode dengan nama ramah yang sesuai dengan setiap operator yang kelebihan beban.

Banyak bahasa tidak mendukung kelebihan beban operator. Untuk alasan ini, direkomendasikan bahwa jenis operator yang kelebihan beban menyertakan metode sekunder dengan nama khusus domain yang sesuai yang menyediakan fungsionalitas yang setara.

Secara khusus, F# adalah salah satu bahasa yang membuatnya canggung untuk memanggil operator konversi implisit.


Juga, saya tidak berpikir hanya memiliki satu cara dalam melakukan sesuatu itu penting. Menurut pendapat saya, lebih penting untuk membuat API nyaman. Jika saya hanya ingin menggabungkan kode hash dari beberapa nilai, saya pikir HashCode.CombineHashCodes(value1, value2, value3) lebih sederhana, lebih pendek dan lebih mudah dipahami daripada new HashCode().Combine(value1).Combine(value2).Combine(value3) .

API metode instan masih berguna untuk kasus yang lebih rumit, tetapi saya pikir kasus yang lebih umum harus memiliki API metode statis yang lebih sederhana.

@svick , poin Anda tentang bahasa lain yang tidak mendukung operator itu sah. Saya menghasilkan, mari kita tambahkan Value lalu.

Saya tidak berpikir hanya memiliki satu cara dalam melakukan sesuatu itu penting.

Itu penting. Jika seseorang melakukannya dengan satu cara, dan membaca kode orang yang melakukannya dengan cara lain, maka dia harus mencari di Google apa yang dilakukan dengan cara lain.

Jika saya hanya ingin menggabungkan kode hash dari beberapa nilai, saya pikir HashCode.CombineHashCodes(value1, value2, value3) lebih sederhana, lebih pendek dan lebih mudah dipahami daripada HashCode() baru.Combine(value1).Combine(value2).Combine( nilai3).

  • Masalah dengan metode statis adalah, karena tidak akan ada kelebihan params int[] , kita harus menambahkan kelebihan untuk setiap arity yang berbeda, yang jauh lebih murah. Jauh lebih baik untuk memiliki satu metode yang mencakup semua kasus penggunaan.
  • Bentuk kedua akan mudah dipahami setelah Anda melihatnya sekali atau dua kali. Faktanya, Anda bisa berargumen itu lebih mudah dibaca, karena lebih mudah untuk dirantai secara vertikal (dan dengan demikian meminimalkan perbedaan ketika sebuah bidang ditambahkan/dihapus):
public override int GetHashCode()
{
    return new HashCode()
        .Combine(_field1)
        .Combine(_field2)
        .Combine(_field3)
        .Combine(_field4);
}

[@svick] Saya tidak berpikir hanya memiliki satu cara dalam melakukan sesuatu itu penting.

Saya pikir meminimalkan jumlah cara Anda dapat melakukan hal yang sama adalah penting karena menghindari kebingungan. Pada saat yang sama, tujuan kami bukanlah 100% bebas tumpang tindih jika membantu mewujudkan tujuan lain, seperti kemampuan untuk ditemukan, kenyamanan, kinerja, atau keterbacaan. Secara umum, tujuan kami adalah meminimalkan konsep, bukan API. Misalnya, beberapa kelebihan beban kurang bermasalah daripada memiliki beberapa metode yang berbeda dengan terminologi yang terputus-putus.

Alasan saya menambahkan metode pabrik adalah untuk memperjelas bagaimana seseorang mendapatkan kode hash awal. Membuat struct kosong diikuti oleh Combine tampaknya tidak terlalu intuitif. Hal yang logis adalah menambahkan .ctor tetapi untuk menghindari tinju, itu harus generik, yang tidak dapat Anda lakukan dengan .ctor. Metode pabrik generik adalah hal terbaik berikutnya.

Efek samping yang bagus adalah tampilannya sangat mirip dengan tampilan struktur data yang tidak dapat diubah dalam kerangka kerja. Dan dalam desain API, kami sangat menyukai konsistensi di atas hampir semua hal lainnya.

[@svick] Jika saya hanya ingin menggabungkan kode hash dari beberapa nilai, saya pikir HashCode.CombineHashCodes(value1, value2, value3) lebih sederhana, lebih pendek dan lebih mudah dipahami daripada HashCode() baru.Combine(value1).Combine(value2 ).Gabungkan(nilai3).

Saya setuju dengan @jamesqo : apa yang saya suka tentang pola pembangun yang

[@jamesqo] Saya tidak berpikir kelebihan menerima string/StringComparison termasuk di sini; HashCode tidak ada hubungannya dengan string

Poin yang adil. Saya menambahkannya karena direferensikan dalam kode @Eilon . Dari pengalaman saya akan mengatakan bahwa string sangat umum. Di sisi lain, saya tidak yakin bahwa menentukan perbandingan adalah. Mari kita tinggalkan untuk saat ini.

[@jamesqo] Kita harus memindahkan kelebihan beban yang lama ke tipe baru. HashCode hanya akan memiliki lebar 32 bit; tidak bisa muat lama.

Itu poin yang bagus. Apakah kita membutuhkan versi long sama sekali? Saya hanya membiarkannya karena disebutkan di atas dan saya tidak terlalu memikirkannya.

Sekarang saya, tampaknya kita harus meninggalkan hanya 32-bit karena itulah yang .NET GetHashCode() adalah tentang. Dalam nada itu, saya bahkan tidak yakin kita harus menambahkan versi uint . Jika Anda menggunakan hashing di luar bidang itu, saya pikir tidak apa-apa untuk mengarahkan orang ke algoritme hashing tujuan yang lebih umum yang kami miliki di System.Security.Cryptography .

```C#
struct publik HashCode
{
Buat HashCode statis publik(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);

}
```

Sekarang saya, sepertinya kita harus meninggalkan hanya 32-bit karena itulah yang dimaksud dengan .NET GetHashCode(). Dalam nada itu, saya bahkan tidak yakin kita harus menambahkan versi uint. Jika Anda menggunakan hashing di luar bidang itu, saya pikir tidak apa-apa untuk mengarahkan orang ke algoritme hashing tujuan yang lebih umum yang kami miliki di System.Security.Cryptography.

@terrajobst Ada berbagai jenis algoritma hashing, kebun binatang nyata. Faktanya, mungkin 70% adalah non-kriptografi berdasarkan desain. Dan mungkin lebih dari setengahnya dirancang untuk menangani 64+ bit (target umum adalah 128/256). Bahwa kerangka memutuskan untuk menggunakan 32 bit saya yakin (saya belum pernah ke sana) adalah karena pada saat itu x86 masih merupakan konsumen besar dan hash digunakan di semua tempat, jadi kinerja pada perangkat keras yang lebih rendah adalah yang terpenting.

Untuk lebih ketatnya, sebagian besar fungsi hash benar-benar didefinisikan pada domain uint , dan bukan pada int karena aturan pemindahannya berbeda. Sebenarnya, jika Anda memeriksa kode yang saya posting sebelumnya, int segera diubah menjadi uint karena itu (dan gunakan optimasi ror/rol ). Untuk berjaga-jaga, jika kita ingin menjadi ketat, satu-satunya hash harus uint , itu dapat dilihat sebagai kekeliruan bahwa kerangka kerja mengembalikan int bawah cahaya itu.

Membatasi ini pada int tidak lebih baik dari apa yang kita miliki saat ini. Jika itu panggilan saya, saya akan mendorong ke tim desain untuk melihat bagaimana kami dapat mengakomodasi varian 128 dan 256 yang mendukung dan fungsi hash yang berbeda (bahkan jika kami akan melemparkan alternatif jangan-buat-pikirkan di bawah sidik jari Anda).

Masalah yang disebabkan oleh penyederhanaan yang berlebihan terkadang lebih buruk daripada masalah desain yang muncul ketika dipaksa untuk menangani hal-hal yang rumit. Menyederhanakan fungsionalitas sedemikian rupa karena pengembang dianggap not being able to deal with having multiple options dapat dengan mudah mengarah ke jalur status SIMD saat ini. Sebagian besar pengembang yang sadar kinerja tidak dapat menggunakannya, dan semua orang juga tidak akan menggunakannya karena sebagian besar tidak berurusan dengan aplikasi sensitif kinerja yang memiliki target throughput yang bagus.

Kasus hashing serupa, domain di mana Anda akan menggunakan 32 bit sangat terbatas (sebagian besar sudah tercakup oleh kerangka itu sendiri), untuk sisanya Anda kurang beruntung.

image

Juga segera setelah Anda harus berurusan dengan lebih dari 75000 elemen, Anda memiliki peluang 50% untuk mengalami tabrakan, dan itu buruk di sebagian besar skenario (dan itu dengan asumsi Anda memiliki fungsi hash yang dirancang dengan baik). Itulah sebabnya 64 bit dan 128 bit digunakan di luar batas struktur runtime.

Dengan desain yang macet pada int kami hanya membahas masalah yang disebabkan oleh tidak adanya surat kabar Monday pada tahun 2000 (jadi sekarang semua orang menulis hashing mereka sendiri yang buruk) tetapi kami tidak akan maju bahkan selangkah pun dalam keadaan seni juga.

Itu 2 sen saya ke dalam diskusi.

@redknightlois , saya pikir kami memahami keterbatasan hash int. Tapi saya setuju dengan @terrajobst : fitur ini harus tentang API untuk menghitung hash dengan tujuan mengembalikannya dari penggantian Object.GetHashCode. Kami mungkin juga memiliki perpustakaan terpisah untuk hashing yang lebih modern, tetapi saya akan mengatakan itu harus menjadi diskusi terpisah, karena perlu menyertakan memutuskan apa yang harus dilakukan dengan Object.GetHashCode dan semua struktur data hashing yang ada.

Kecuali menurut Anda masih menguntungkan untuk melakukan penggabungan hash dalam 128 bit dan kemudian dikonversi ke int sehingga hasilnya dapat dikembalikan dari GetHahsCode.

@KrzysztofCwalina Saya setuju ini adalah dua pendekatan yang berbeda. Salah satunya adalah untuk memperbaiki masalah yang disebabkan pada tahun 2000; yang berbeda adalah untuk mengatasi masalah hashing umum. Jika kita semua setuju ini adalah solusi untuk yang pertama, diskusi selesai. Namun, untuk diskusi desain untuk tonggak "Masa Depan" saya merasa itu akan gagal, terutama karena apa yang akan kita lakukan di sini akan berdampak pada diskusi di masa depan. Membuat kesalahan di sini, akan berdampak.

@redknightlois , saya akan mengusulkan yang berikut: mari kita rancang API seolah-olah kita tidak perlu khawatir tentang masa depan. Kemudian, mari kita bahas pilihan desain mana yang menurut kami akan menyebabkan masalah untuk API di masa mendatang. Selain itu, yang dapat kami lakukan adalah menambahkan API c2000 ke corfx dan secara paralel mencoba bereksperimen dengan API masa depan di corfxlab, yang akan mengungkap masalah apa pun yang terkait dengan penambahan tersebut, jika kami ingin melakukannya.

@redknightlois

Membuat kesalahan di sini, akan berdampak.

Saya pikir jika, di masa depan kami ingin mendukung skenario yang lebih maju, maka kami dapat melakukannya dalam tipe terpisah dari HashCode . Keputusan di sini seharusnya tidak terlalu berdampak pada kasus-kasus itu.

Saya membuat masalah yang berbeda untuk mulai mengatasinya.

@redknightlois :+1:. Btw, Anda merespons sebelum saya dapat mengedit komentar saya, tetapi saya benar-benar mencoba ide Anda (di atas) agar hash berfungsi dengan jenis apa pun (int, panjang, desimal, dll.) dan merangkum logika hashing inti dalam sebuah struct: https://github.com/jamesqo/HashApi (penggunaan sampel ada di sini ). Tetapi, memiliki dua parameter tipe generik akhirnya menjadi terlalu rumit, dan inferensi tipe kompiler akhirnya tidak berfungsi ketika saya mencoba menggunakan API. Jadi ya, ide yang bagus untuk hashing yang lebih maju menjadi masalah terpisah untuk saat ini.

@terrajobst API tampaknya hampir siap, tetapi ada 1 atau 2 hal lagi yang ingin saya ubah.

  • Awalnya saya tidak menginginkan metode pabrik statis, karena HashCode.Create(x) memiliki efek yang sama dengan new HashCode().Combine(x) . Tapi, saya berubah pikiran tentang itu karena itu berarti 1 hash tambahan. Sebaliknya, mengapa kita tidak mengganti nama Create menjadi Combine ? Tampaknya agak menjengkelkan harus mengetik satu hal untuk bidang pertama dan yang lain untuk bidang kedua.
  • Saya pikir kita harus memiliki HashCode mengimplementasikan IEquatable<HashCode> dan mengimplementasikan beberapa operator kesetaraan. Jangan ragu untuk memberi tahu saya jika Anda memiliki keberatan.

(Semoga) proposal akhir:

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 berkata:

Poin yang adil. Saya menambahkannya karena direferensikan dalam kode @Eilon . Dari pengalaman saya akan mengatakan bahwa string sangat umum. Di sisi lain, saya tidak yakin bahwa menentukan perbandingan adalah. Mari kita tinggalkan untuk saat ini.

Ini sebenarnya sangat penting: membuat hash untuk string sering kali melibatkan pertimbangan tujuan dari string tersebut, yang melibatkan budaya dan kepekaan huruf besar/kecilnya. StringComparer bukan tentang perbandingan semata, melainkan tentang menyediakan implementasi GetHashCode spesifik yang peka terhadap budaya/kasus.

Tanpa API ini, Anda perlu melakukan sesuatu yang aneh seperti:

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

Dan itu penuh dengan alokasi, mengikuti pola kepekaan budaya yang buruk, dll.

@Eilon dalam kasus seperti itu saya berharap kode harus secara eksplisit memanggil string.GetHashCode(StringComparison comparison) yang merupakan budaya/case-aware dan meneruskan hasilnya sebagai int ke Combine .

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

@Eilon , Anda bisa menggunakan StringComparer.InvariantCultureIgnoreCase.GetHashCode.

Itu tentu lebih baik dalam hal alokasi, tetapi panggilan itu tidak bagus untuk dilihat... Kami telah menggunakan seluruh ASP.NET di mana hash perlu menyertakan string kultur/peka huruf besar-kecil.

Cukup adil, menggabungkan semua yang dikatakan di atas, bagaimana dengan bentuk ini:

``` C#
namespace System.Collections.Generic
{
struct publik HashCode : IEquatable
{
Gabungkan HashCode publik statis (int hash);
Gabungkan HashCode publik statis(T obj);
Gabungkan HashCode statis publik (teks string, perbandingan 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();
}

}

// Penggunaan:

menimpa publik int GetHashCode()
{
kembalikan HashCode.Combine(_field1)
.Gabungkan(_field2)
.Gabungkan(_field3)
.Combine(_field4);
}
```

kirimkan! :-)

@terrajobst _Tunggu--_ tidak bisakah Combine(string, StringComparison) diimplementasikan sebagai metode ekstensi?

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:
                ...
        }
    }
}

Saya lebih suka itu menjadi metode ekstensi daripada bagian dari tanda tangan tipe. Namun, jika Anda atau @Elion benar-benar berpikir ini harus menjadi metode

( edit: Juga System.Numerics mungkin merupakan namespace yang lebih baik, kecuali kita memiliki tipe terkait hash di Collections.Generic hari ini yang tidak saya ketahui.)

LGTM. Aku akan pergi ekstensi.

Ya, itu bisa menjadi metode ekstensi, tetapi masalah apa yang dipecahkannya?

@terrajobst

Ya, itu bisa menjadi metode ekstensi, tetapi masalah apa yang dipecahkannya?

Saya menyarankan dalam kode ASP.NET. Jika itu umum untuk kasus penggunaan mereka, maka tidak apa-apa, tetapi itu mungkin tidak berlaku untuk perpustakaan/aplikasi lain. Jika ternyata ini cukup umum nanti, kami selalu dapat mengevaluasi kembali dan memutuskan untuk menambahkannya dalam proposal terpisah.

Mhhh ini intinya. Setelah ditentukan, itu akan menjadi bagian dari tanda tangan. Buang komentarnya. Tidak apa-apa seperti itu.

Menggunakan metode ekstensi berguna untuk kasus di mana:

  1. ini adalah tipe yang sudah ada yang ingin kami tingkatkan tanpa harus mengirimkan pembaruan ke tipe itu sendiri
  2. memecahkan masalah layering
  3. pisahkan API super umum dari API yang lebih jarang digunakan.

Saya tidak berpikir (1) atau (2) berlaku di sini. (3) hanya akan membantu jika kita akan memindahkan kode ke Majelis yang berbeda dari HashCode atau jika kita memindahkannya ke namespace yang berbeda. Saya berpendapat bahwa string cukup umum sehingga tidak sepadan. Faktanya, saya bahkan berpendapat bahwa mereka sangat umum sehingga memperlakukan mereka sebagai kelas satu lebih masuk akal daripada mencoba memisahkan mereka secara artifisial pada tipe ekstensi.

@terrajobst , untuk lebih jelasnya saya menyarankan membuang string API sama sekali, dan menyerahkannya ke ASP.NET untuk menulis metode ekstensi mereka sendiri untuk string.

Saya berpendapat bahwa string cukup umum sehingga tidak sepadan. Faktanya, saya bahkan berpendapat bahwa mereka sangat umum sehingga memperlakukan mereka sebagai kelas satu lebih masuk akal daripada mencoba memisahkan mereka secara artifisial pada tipe ekstensi.

Ya, tetapi seberapa umumkah seseorang ingin mendapatkan kode hash non-ordinal dari sebuah string, yang merupakan satu-satunya skenario yang tidak ditangani oleh kelebihan Combine<T> ada? (misalnya Seseorang yang memanggil StringComparer.CurrentCulture.GetHashCode dalam penggantiannya?) Saya mungkin salah, tetapi saya belum melihat banyak.

Maaf untuk pushback ini; hanya saja setelah API ditambahkan, tidak ada jalan untuk kembali.

ya, tetapi seberapa umum seseorang ingin mendapatkan kode hash non-ordinal dari sebuah string

Saya mungkin bias, tetapi invarians kasus cukup populer. Tentu, tidak banyak (jika ada) yang peduli dengan kode hash khusus budaya tetapi kode hash yang mengabaikan casing benar-benar dapat saya lihat -- dan sepertinya itulah yang dicari oleh StringComparison.OrdinalIgnoreCase ).

Maaf untuk pushback ini; hanya saja setelah API ditambahkan, tidak ada jalan untuk kembali.

Tidak main-main Setuju, tetapi bahkan jika API tidak digunakan sebanyak itu berguna dan tidak menyebabkan kerusakan apa pun.

@terrajobst Ok, mari kita tambahkan :+1: Edisi terakhir: Saya menyebutkan ini di atas, tetapi bisakah kita membuat Numerik namespace daripada Collections.Generic? Jika kami menambahkan lebih banyak jenis terkait hashing di masa mendatang seperti yang disarankan @redknightlois , saya pikir itu akan menjadi kesalahan penamaan di Koleksi.

Aku menyukainya. 🍔

Saya tidak berpikir Hashing jatuh secara konseptual ke dalam Koleksi. Bagaimana dengan System.Runtime?

Saya akan menyarankan hal yang sama, atau bahkan System. Ini juga bukan Numerik.

@karelz , System.Runtime bisa bekerja. @redknightlois Sistem akan nyaman, karena kemungkinan Anda sudah mengimpor namespace itu. Entah apakah itu yang sesuai (sekali lagi, jika lebih banyak jenis hashing ditambahkan).

Kita tidak boleh memasukkannya ke dalam System.Runtime karena ini untuk kasus esoteris dan cukup khusus. Saya berbicara dengan @KrzysztofCwalina dan kami berdua berpikir itu salah satu dari keduanya:

  • System
  • System.Collections.*

Kami berdua condong ke arah System .

Jika yang kita butuhkan adalah alasan mengapa pergi untuk System Saya dapat mencoba pembenaran. Kami membangun HashCode untuk membantu implementasi object.GetHashCode() , kedengarannya cocok bahwa keduanya akan berbagi namespace.

@terrajobst Saya pikir System harus menjadi namespace, kalau begitu. ayo :kirim:

Memperbarui spesifikasi API dalam deskripsi.

[@redknightlois] Jika yang kita butuhkan adalah alasan mengapa harus pergi untuk System Saya dapat mencoba pembenaran. Kami membangun HashCode untuk membantu implementasi object.GetHashCode() , kedengarannya cocok bahwa keduanya akan berbagi namespace.

Itulah alasan yang saya dan @KrzysztofCwalina gunakan juga. Terjual!

@jamesqo

Saya berasumsi Anda ingin memberikan PR dengan implementasinya juga?

@terrajobst Ya, pasti. Terima kasih telah meluangkan waktu untuk meninjau ini!

Iya tentu saja.

Manis. Dalam hal ini saya akan menyerahkannya kepada Anda. Itu bagus untukmu @karelz?

Terima kasih telah meluangkan waktu untuk meninjau ini!

Terima kasih telah meluangkan waktu untuk bekerja sama dengan kami dalam bentuk API. Ini bisa menjadi proses yang menyakitkan untuk bolak-balik. Kami sangat menghargai kesabaran Anda!

Dan saya berharap untuk menghapus implementasi ASP.NET Core dan menggunakan ini sebagai gantinya

Gabungkan HashCode statis publik (teks string, perbandingan StringComparison);
Gabungkan HashCode publik (teks string, perbandingan StringComparison);

Nit: Metode pada String yang menggunakan StringComparison (misalnya Equals , Compare , StartsWith , EndsWith , dll .) gunakan comparisonType sebagai nama parameter, bukan comparison . Haruskah parameter diberi nama comparisonType sini juga agar konsisten?

@justinvp , yang sepertinya lebih seperti kesalahan penamaan dalam metode String; Type berlebihan. Saya tidak berpikir kita harus membuat nama parameter di API baru lebih bertele-tele hanya untuk "mengikuti preseden" dengan yang lama.

Sebagai titik data lainnya, xUnit memilih untuk menggunakan comparisonType juga.

@justinvp Anda telah meyakinkan saya. Sekarang saya memikirkannya secara intuitif, "tidak peka huruf besar-kecil" atau "bergantung pada budaya" adalah 'jenis' perbandingan. Aku akan mengubah nama.

Saya setuju dengan bentuknya, tetapi mengenai StringComparison, alternatif yang mungkin:

Jangan sertakan:

``` C#
Gabungkan HashCode statis publik (teks string, perbandingan StringComparison);
Gabungkan HashCode publik (teks string, perbandingan StringComparison);

Instead, add a method:

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

Kemudian alih-alih menulis:

``` C#
menimpa publik int GetHashCode()
{
kembalikan HashCode.Combine(_field1)
.Gabungkan(_field2)
.Gabungkan(_field3)
.Combine(_field4, _comparison);
}

you write:

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

Ya, ini sedikit lebih lama, tetapi ini memecahkan masalah yang sama tanpa memerlukan dua metode khusus pada HashCode (yang baru saja kami promosikan ke Sistem), dan Anda mendapatkan metode pembantu statis yang dapat digunakan dalam situasi lain yang tidak terkait. Itu juga membuatnya serupa dengan bagaimana Anda akan menggunakannya jika Anda sudah memiliki StringComparer (karena kita tidak berbicara tentang kelebihan pembanding):

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

@stephentoub , FromComparison terdengar seperti ide yang bagus. Saya sebenarnya mengusulkan ke atas di utas untuk menambahkan api string.GetHashCode(StringComparison) , yang membuat contoh Anda lebih sederhana (dengan asumsi string non-null):

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

@Elion mengatakan itu menambahkan terlalu banyak panggilan.

(edit: membuat proposal untuk api Anda.)

Saya juga tidak suka menambahkan 2 metode khusus pada HashCode untuk string.
@Eilon Anda menyebutkan pola yang digunakan di ASP.NET Core itu sendiri. Menurut Anda seberapa banyak pengembang eksternal akan menggunakannya?

@jamesqo terima kasih telah mendorong desain! Seperti yang dikatakan @terrajobst , kami menghargai bantuan dan kesabaran Anda. API kecil yang mendasar terkadang membutuhkan waktu untuk beralih :).

Mari kita lihat di mana kita mendarat dengan umpan balik API terakhir ini, lalu kita bisa melanjutkan implementasinya.

Haruskah ada:

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

?

(Maaf jika itu sudah diberhentikan dan saya melewatkannya di sini).

@stephentoub berkata:

menulis:

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

Ya, ini sedikit lebih lama, tetapi ini memecahkan masalah yang sama tanpa memerlukan dua metode khusus pada HashCode (yang baru saja kami promosikan ke Sistem), dan Anda mendapatkan metode pembantu statis yang dapat digunakan dalam situasi lain yang tidak terkait. Itu juga membuatnya serupa dengan bagaimana Anda akan menggunakannya jika Anda sudah memiliki StringComparer (karena kita tidak berbicara tentang kelebihan pembanding):


Yah, itu bukan hanya sedikit lebih lama, itu seperti waaay super lebih lama, dan tidak memiliki kemampuan untuk ditemukan.

Apa hambatan untuk menambahkan metode ini? Jika bermanfaat, dapat dengan jelas diimplementasikan dengan benar, tidak memiliki ambiguitas dalam fungsinya, mengapa tidak menambahkannya?

Memiliki metode pembantu/konversi statis tambahan baik-baik saja - meskipun saya tidak yakin saya akan menggunakannya - tetapi mengapa dengan mengorbankan metode kenyamanan?

mengapa dengan mengorbankan metode kenyamanan?

Karena tidak jelas bagi saya metode kenyamanan sangat dibutuhkan di sini. Saya mendapatkan bahwa ASP.NET melakukannya di berbagai tempat. Berapa banyak tempat? Dan di berapa banyak tempat itu sebenarnya variabel StringComparison yang Anda miliki daripada nilai yang diketahui? Dalam hal ini Anda bahkan tidak memerlukan pembantu yang saya sebutkan dan hanya bisa melakukan:

``` C#
.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);

dan sebenarnya lebih cepat, karena kita tidak perlu bercabang di dalam Combine untuk melakukan hal yang sama persis dengan yang bisa ditulis oleh dev. Apakah kode tambahan itu sangat merepotkan sehingga perlu ditambahkan kelebihan khusus untuk satu kasus itu? Mengapa tidak kelebihan untuk StringComparer? Mengapa tidak kelebihan untuk EqualityComparer? Mengapa tidak kelebihan beban yang membutuhkan Func<T, int> ? Pada titik tertentu Anda menarik garis dan mengatakan "nilai yang diberikan kelebihan beban ini tidak sepadan", karena semua yang kami tambahkan dikenakan biaya, apakah itu biaya pemeliharaan, biaya ukuran kode,, biaya apa pun , dan jika pengembang benar-benar membutuhkan kasing ini, sangat sedikit kode tambahan yang harus ditangani pengembang dengan kasing khusus yang lebih sedikit. Jadi saya menyarankan mungkin tempat yang tepat untuk menarik garis adalah sebelum kelebihan ini daripada sesudahnya (tetapi seperti yang saya nyatakan di awal tanggapan saya sebelumnya, "Saya setuju dengan bentuk ini", dan menyarankan alternatif) .

Inilah pencarian yang saya lakukan: https://github.com/search?p=2&q=user%3Aaspnet+hashcodecombiner&type=Code&utf8=%E2%9C%93

Dari ~100 kecocokan, bahkan hanya dari beberapa halaman pertama, hampir setiap kasus penggunaan memiliki string, dan dalam beberapa kasus menggunakan berbagai jenis perbandingan string:

  1. Ordinal: https://github.com/aspnet/Razor/blob/77ed9f22fc8894fbce796bb8a704d6cd03a3b226/src/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/TagHelperAttributeDescriptorComparer.
  2. Ordinal + IgnoreCase: https://github.com/aspnet/Razor/blob/bdbb854bdbde260b3c70f565a93ebbb185a7c5a7/src/Microsoft.AspNetCore.Razor/Compilation/TagHelpers/TagHelperRequiredAttribute.Descriptor
  3. Ordinal: https://github.com/aspnet/Razor/blob/bdbb854bdbde260b3c70f565a93ebbb185a7c5a7/src/Microsoft.AspNetCore.Razor/Chunks/Generators/AttributeBlockChunkGenerator.cs#L58
  4. Ordinal: https://github.com/aspnet/Razor/blob/77ed9f22fc8894fbce796bb8a704d6cd03a3b226/src/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/TagHelperDesignTimeDescriptor.cs#L41Comparer.cs
  5. Ordinal: https://github.com/aspnet/Razor/blob/dbcb6901209859e471c9aa978912cf7d6c178668/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AttributeBlockChunkGenerator.cs#L56
  6. Ordinal: https://github.com/aspnet/Razor/blob/77ed9f22fc8894fbce796bb8a704d6cd03a3b226/src/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveTagHelper.cs#L62Comparer
  7. Ordinal + IgnoreCase: https://github.com/aspnet/dnx/blob/bebc991012fe633ecac69675b2e892f568b927a5/src/Microsoft.Dnx.Tooling/NuGet/Core/PackageSource/PackageSource.cs#L107
  8. Ordinal: https://github.com/aspnet/Razor/blob/bdbb854bdbde260b3c70f565a93ebbb185a7c5a7/src/Microsoft.AspNetCore.Razor/Tokenizer/Symbols/SymbolBase.cs#L52
  9. Ordinal: https://github.com/aspnet/Razor/blob/77ed9f22fc8894fbce796bb8a704d6cd03a3b226/src/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveTagHelperAttributeComparer.cs#L39
  10. Ordinal: https://github.com/aspnet/Razor/blob/77ed9f22fc8894fbce796bb8a704d6cd03a3b226/src/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/TagHelperAttributeDesignTimeDescriptorComparer

(Dan lusinan lainnya.)

Jadi sepertinya dalam basis kode ASP.NET Core ini adalah pola yang sangat umum. Tentu saja saya tidak dapat berbicara dengan sistem lain.

Dari ~100 pertandingan

Setiap satu dari 10 yang Anda daftarkan (saya tidak melihat sisa pencarian) secara eksplisit menentukan perbandingan string, daripada menariknya dari variabel, jadi bukankah kita hanya berbicara tentang perbedaan antara, misalnya:

``` C#
.Combine(Nama, StringComparison.OrdinalIgnoreCase)

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

? Itu bukan "waaay super lagi" dan lebih efisien, kecuali saya melewatkan sesuatu.

Bagaimanapun, seperti yang telah saya nyatakan, saya hanya menyarankan agar kita benar-benar mempertimbangkan apakah kelebihan beban ini diperlukan. Jika kebanyakan orang percaya, dan kami tidak hanya mempertimbangkan basis kode ASP.NET kami sendiri, baiklah.

Terkait, apa perilaku yang kami rencanakan untuk input nol? Bagaimana dengan int==0? Saya dapat mulai melihat lebih banyak manfaat dari kelebihan string jika kita mengizinkan null untuk dilewatkan, karena saya percaya StringComparer.GetHashCode biasanya melempar untuk input nol, jadi jika ini benar-benar umum, itu mulai menjadi lebih rumit jika penelepon memiliki ke nol kasus khusus. Tapi itu kemudian juga menimbulkan pertanyaan tentang perilaku apa yang akan terjadi ketika null diberikan. Apakah 0 dicampur dengan kode hash seperti nilai lainnya? Apakah itu diperlakukan sebagai nop dan kode hash dibiarkan begitu saja?

Saya pikir pendekatan umum terbaik untuk nol adalah mencampurkan nol. Untuk satu elemen nol yang ditambahkan memilikinya sebagai nop akan lebih baik, tetapi jika seseorang memberi makan secara berurutan maka menjadi lebih bermanfaat untuk memiliki 10 hash nol berbeda dengan 20.

Memang, suara saya berasal dari perspektif basis kode ASP.NET Core, di mana memiliki kelebihan yang sadar akan string akan sangat membantu. Hal-hal tentang panjang garis sebenarnya bukan perhatian utama saya, melainkan tentang kemampuan untuk ditemukan.

Jika kelebihan string-aware tidak tersedia di sistem, kami hanya akan menambahkan metode ekstensi internal di ASP.NET Core dan menggunakannya.

Jika kelebihan string-aware tidak tersedia di sistem, kami hanya akan menambahkan metode ekstensi internal di ASP.NET Core dan menggunakannya.

Saya pikir itu akan menjadi solusi yang bagus untuk saat ini, sampai kita melihat lebih banyak bukti bahwa API semacam itu diperlukan secara umum, juga di luar basis kode ASP.NET Core.

Saya harus mengatakan bahwa saya tidak melihat nilai dalam menghilangkan kelebihan string . Itu tidak mengurangi kerumitan apa pun, itu tidak membuat kode lebih efisien, dan itu tidak menghalangi kita untuk meningkatkan area lain, seperti menyediakan metode yang mengembalikan StringComparer dari StringComparison . Gula sintaksis _tidak_ penting, karena .NET selalu tentang membuat kasus umum menjadi mudah. Kami juga ingin memandu pengembang untuk melakukan hal yang benar dan jatuh ke dalam lubang kesuksesan.

Kita perlu menghargai bahwa string itu spesial dan sangat umum. Dengan menambahkan kelebihan yang mengkhususkan mereka, kami mencapai dua hal:

  1. Kami membuat skenario seperti @Eilon jauh lebih mudah.
  2. Kami membuatnya dapat ditemukan bahwa mempertimbangkan perbandingan untuk string itu penting, terutama casing.

Kita juga perlu mempertimbangkan bahwa pembantu boilerplate umum seperti metode ekstensi @Eilon yang disebutkan di atas bukanlah hal yang baik, itu hal yang buruk. Ini menghasilkan metode pembantu salin & tempel yang terbuang selama berjam-jam dan kemungkinan akan menghasilkan kode dan bug yang tidak perlu jika tidak dilakukan dengan benar.

Namun, jika perhatian utama adalah tentang casing khusus string , lalu bagaimana dengan ini:

``` C#
struct publik HashCode : IEquatable
{
Gabungkan HashCode publik(T obj, IEqualityComparerpembanding);
}

// Penggunaan
kembalikan HashCode.Combine(_numberField)
.Combine(_stringField, StringComparer.OrdinalIgnoreCase);
```

@terrajobst , kompromi Anda cerdas. Saya suka bagaimana Anda tidak lagi harus memanggil GetHashCode secara eksplisit atau membuat kumpulan tanda kurung tambahan dengan pembanding khusus.

(edit: Saya kira saya harus benar-benar memuji @JonHanna karena dia menyebutkannya sebelumnya di utas? )

@JonHanna Ya, kami juga akan melakukan hash null input sebagai 0.

Maaf mengganggu pembicaraan di sini. Tapi, di mana saya harus meletakkan tipe baru? @mellinoe @ericstj @weshaggard , apakah Anda menyarankan saya membuat Majelis/paket baru untuk jenis ini seperti System.HashCode , atau haruskah saya menambahkannya ke Majelis yang ada seperti System.Runtime.Extensions ? Terima kasih.

Kami baru saja memfaktorkan ulang tata letak perakitan di .NET Core sedikit; Saya menyarankan untuk meletakkannya di mana pembanding beton hidup, yang tampaknya menunjukkan System.Runtime.Extensions .

@weshaggard?

@terrajobst Mengenai proposal itu sendiri, saya baru tahu bahwa kami tidak dapat memberi nama static & instance overloads Combine , sayangnya. 😢

Hasil berikut dalam kesalahan kompiler karena instance dan metode statis tidak dapat memiliki nama yang sama:

using System;
using System.Collections.Generic;

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

    public static void Combine(int i)
    {
    }
}

Sekarang kita memiliki 2 pilihan:

  • Ganti nama kelebihan beban statis menjadi sesuatu yang berbeda seperti Create , Seed , dll.
  • Pindahkan kelebihan beban statis ke kelas statis lain:
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);

Saya lebih suka yang kedua. Sangat disayangkan bahwa kita harus mengatasi masalah ini, tapi... pikiran?

Memisahkan logika menjadi 2 jenis terdengar aneh bagi saya - untuk menggunakan HashCode Anda harus membuat koneksi dan mulai dengan kelas Hash sebagai gantinya.

Saya lebih suka menambahkan metode Create (atau Seed atau Init ).
Saya juga akan menambahkan no-args overload HashCode.Create().Combine(_field1).Combine(_field2) .

@karelz , saya tidak berpikir kita harus menambahkan metode pabrik jika itu bukan nama yang sama. Kami hanya harus menawarkan konstruktor tanpa parameter, new , karena lebih alami. Selain itu, e tidak dapat mencegah orang menulis new HashCode().Combine karena ini adalah struct.

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

Ini melakukan kombinasi ekstra dengan kode hash 0 dan _field1 , alih-alih menginisialisasi langsung dari kode hash. Namun, efek samping dari hash saat ini yang kami gunakan , adalah bahwa 0 dilewatkan sebagai parameter pertama, itu akan diputar ke nol dan ditambahkan ke nol. Dan ketika 0 di-xor dengan kode hash pertama, itu hanya akan menghasilkan kode hash pertama. Jadi, jika JIT bagus dalam melipat konstan (dan saya percaya itu mengoptimalkan xor ini), pada dasarnya ini harus setara dengan inisialisasi langsung.

API yang Diusulkan (spesifikasi yang diperbarui):

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

@redknightlois @JonHanna @stephentoub @Eilon , apakah Anda memiliki pendapat tentang metode pabrik vs. menggunakan konstruktor default? Saya menemukan bahwa kompiler tidak mengizinkan kelebihan Combine statis karena itu bertentangan dengan metode instance, jadi kami memiliki opsi untuk keduanya

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

// or, using default constructor

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

Keuntungan yang pertama adalah sedikit lebih terser. Keuntungan yang kedua adalah akan memiliki penamaan yang konsisten sehingga Anda tidak perlu menulis sesuatu yang berbeda untuk bidang pertama.

Kemungkinan lain adalah dua tipe berbeda, satu dengan Combine factory, satu dengan instance Combine (atau yang kedua sebagai ekstensi pada tipe pertama).

Saya tidak yakin mana yang saya lebih suka TBH.

@JonHanna , ide kedua Anda dengan instance overloads menjadi metode ekstensi terdengar bagus. Yang mengatakan, hc.Combine(obj) dalam hal ini mencoba untuk mengambil kelebihan statis: TryRoslyn .

Saya mengusulkan memiliki kelas statis sebagai titik masuk beberapa komentar di atas, yang mengingatkan saya ... @karelz ,

Memisahkan logika menjadi 2 jenis terdengar aneh bagi saya - untuk menggunakan HashCode Anda harus membuat koneksi dan mulai dengan kelas Hash sebagai gantinya.

Hubungan apa yang harus dibuat orang? Bukankah kita akan memperkenalkan mereka ke Hash terlebih dahulu, dan kemudian dari sana mereka dapat mencapai HashCode ? Saya tidak berpikir menambahkan kelas statis baru akan menjadi masalah.

Memisahkan logika menjadi 2 jenis terdengar aneh bagi saya - untuk menggunakan HashCode Anda harus membuat koneksi dan mulai dengan kelas Hash sebagai gantinya.

Kita bisa mempertahankan tipe tingkat atas HashCode dan hanya membuat sarang struct. Ini akan memungkinkan penggunaan yang diinginkan sambil menjaga "titik masuk" API ke satu jenis tingkat atas, misalnya:

``` c#
Sistem ruang nama
{
HashCode kelas statis publik
{
Gabungkan HashCodeValue publik statis (int hash);
Gabungkan HashCodeValue statis publik(T obj);
Gabungkan HashCodeValue statis publik(T obj, IEqualityComparerpembanding);

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

}
```

Sunting: Meskipun, mungkin memerlukan nama yang lebih baik daripada HashCodeValue untuk tipe bersarang jika kita menempuh jalur ini karena HashCodeValue.Value sedikit berlebihan, bukan berarti Value akan sangat digunakan sering. Mungkin kami bahkan tidak memerlukan properti Value -- Anda bisa mendapatkan Value melalui GetHashCode() jika Anda tidak ingin menggunakan int .

@justinvp Apa masalahnya dengan memiliki dua tipe terpisah? Sistem ini tampaknya berfungsi dengan baik untuk LinkedList<T> dan LinkedListNode<T> , misalnya.

Apa masalahnya dengan memiliki dua tipe yang terpisah sejak awal?

Ada dua masalah dengan dua tipe tingkat atas:

  1. Jenis mana yang merupakan "titik masuk" untuk API? Jika namanya Hash dan HashCode , yang mana yang Anda mulai? Tidak jelas dari nama-nama itu. Dengan LinkedList<T> dan LinkedListNode<T> cukup jelas mana yang merupakan titik masuk utama, LinkedList<T> , dan mana yang merupakan penolong.
  2. Mencemari ruang nama System . Ini tidak terlalu mengkhawatirkan seperti (1), tetapi sesuatu yang perlu diingat saat kami mempertimbangkan untuk mengekspos fungsionalitas baru di ruang nama System .

Bersarang membantu mengurangi kekhawatiran ini.

@justinvp

Jenis mana yang merupakan "titik masuk" untuk API? Jika namanya Hash dan HashCode, yang mana yang Anda mulai? Tidak jelas dari nama-nama itu. Dengan LinkedListdan LinkedListNodecukup jelas mana yang merupakan titik masuk utama, LinkedList, dan yang merupakan pembantu.

Oke, poin yang cukup adil. Bagaimana jika kita menamai tipe Hash dan HashValue , bukan tipe bersarang? Apakah itu cukup menunjukkan hubungan penaklukan antara kedua tipe itu?

Jika kita melakukannya, maka metode pabrik menjadi lebih singkat: Hash.Combine(field1).Combine(field2) . Plus, menggunakan tipe struct itu sendiri masih praktis. Misalnya, seseorang mungkin ingin mengumpulkan daftar hash, dan untuk mengomunikasikannya kepada pembaca, List<HashValue> digunakan sebagai ganti List<int> . Ini mungkin tidak bekerja dengan baik jika kita membuat tipe bersarang: List<HashCode.HashCodeValue> (bahkan List<Hash.Value> agak membingungkan pada pandangan pertama).

Mencemari namespace Sistem. Ini tidak terlalu mengkhawatirkan seperti (1), tetapi sesuatu yang perlu diingat saat kami mempertimbangkan untuk mengekspos fungsionalitas baru di ruang nama Sistem.

Saya setuju, tetapi saya juga berpikir penting bagi kita untuk mengikuti konvensi & tidak mengorbankan kemudahan penggunaan. Misalnya, satu-satunya API BCL yang dapat saya pikirkan di mana kami memiliki tipe bersarang (koleksi yang tidak dapat diubah tidak dihitung, mereka tidak sepenuhnya merupakan bagian dari kerangka kerja) adalah List<T>.Enumerator , di mana kami secara aktif ingin menyembunyikan bersarang ketik karena ditujukan untuk penggunaan kompiler. Kami tidak ingin melakukan itu dalam kasus ini.

Mungkin kami bahkan tidak memerlukan properti Nilai -- Anda bisa mendapatkan Nilai melalui GetHashCode() jika Anda tidak ingin menggunakan int.

Saya memikirkan itu sebelumnya. Tetapi bagaimana pengguna akan mengetahui bahwa tipe tersebut menimpa GetHashCode , atau bahwa ia memiliki operator implisit?

API yang Diusulkan

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

Bagaimana jika kita menamai tipe Hash dan HashValue, bukan tipe bersarang?

Hash sepertinya terlalu umum untuk saya. Saya pikir kita perlu memiliki HashCode dalam nama API titik masuk karena tujuan yang dimaksudkan adalah untuk membantu mengimplementasikan GetHashCode() , bukan GetHash() .

seseorang mungkin ingin mengumpulkan daftar hash, dan mengomunikasikan ini kepada pembaca Daftardigunakan sebagai pengganti Daftar. Ini mungkin tidak berfungsi dengan baik jika kita membuat tipe bersarang: Daftar(bahkan Daftaragak membingungkan pada pandangan pertama).

Ini sepertinya kasus penggunaan yang tidak mungkin -- tidak yakin kita harus mengoptimalkan desain untuk itu.

satu-satunya API BCL yang dapat saya pikirkan di mana kami memiliki tipe bersarang

TimeZoneInfo.AdjustmentRule dan TimeZoneInfo.TransitionTime adalah contoh dalam BCL yang sengaja ditambahkan sebagai tipe bersarang.

@justinvp

Saya pikir kita perlu memiliki HashCode atas nama API titik masuk karena tujuan yang dimaksudkan adalah untuk membantu mengimplementasikan GetHashCode(), bukan GetHash().

👍 Saya melihat.

Saya telah memikirkan hal-hal sedikit lebih banyak. Tampaknya masuk akal untuk memiliki struct bersarang; seperti yang Anda sebutkan, kebanyakan orang tidak akan pernah melihat tipe yang sebenarnya. Hanya satu hal: Saya pikir tipenya harus disebut Seed , daripada HashCodeValue . Konteks namanya sudah tersirat oleh kelas yang memuatnya.

API yang Diusulkan

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 Ada keberatan atau masalah implementasi dengan memiliki public readonly int Value sebagai gantinya? Masalah dengan Seed adalah bahwa secara teknis ini bukan benih setelah penggabungan pertama.

Juga setuju dengan @justinvp , Hash harus dicadangkan untuk menangani hash. Ini diperkenalkan menyederhanakan berurusan dengan HashCode sebagai gantinya.

@redknightlois Untuk lebih jelasnya, kami berbicara tentang nama struct, bukan nama properti.

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

Penggunaan:
c# int hashCode = HashCode.Combine(field1).Combine(name, StringComparison.OrdinalIgnoreCase).Value; int hashCode = (int)HashCode.Combine(field1).Combine(field2);

Masalah dengan Seed adalah bahwa secara teknis ini bukan benih setelah penggabungan pertama.

Ini adalah benih untuk kombinasi berikutnya, yang menghasilkan benih baru.

Adakah keberatan atau masalah implementasi dengan memiliki Nilai int readonly publik saja?

Mengapa? int Value { get; } lebih idiomatis dan dapat dengan mudah digarisbawahi.

Ini adalah benih untuk kombinasi berikutnya, yang menghasilkan benih baru.

Bukankah itu akan menjadi bibit? ;)

@jamesqo Dalam pengalaman saya ketika dikelilingi dengan properti kode kompleks cenderung menghasilkan kode yang lebih buruk daripada bidang (di antaranya, non inline). Juga bidang readonly dari satu int pada struct diterjemahkan langsung dalam register dan akhirnya ketika JIT menggunakan readonly untuk optimasi (yang belum dapat menemukan penggunaannya mengenai kode-gen); ada pengoptimalan yang dapat diizinkan karena dapat dianggap sebagai readonly. Dari sudut pandang penggunaan, sebenarnya tidak ada bedanya dengan pengambil tunggal.

EDIT: Plus itu juga mendorong gagasan bahwa struct itu benar-benar tidak dapat diubah.

Dalam pengalaman saya ketika dikelilingi dengan properti kode kompleks cenderung menghasilkan kode yang lebih buruk daripada bidang (di antaranya, non inline).

Jika Anda menemukan satu bangunan non-Debug di mana properti yang diimplementasikan secara otomatis tidak selalu sejajar, maka itu adalah masalah JIT dan harus diperbaiki.

Juga bidang readonly dari satu int pada struct diterjemahkan langsung dalam register

ada pengoptimalan yang dapat diizinkan karena dapat dianggap sebagai readonly.

Bidang pendukung dari struct ini hanya dapat dibaca; API akan menjadi pengakses.

Saya tidak berpikir menggunakan properti akan merusak kinerja dengan cara apa pun di sini.

@jamesqo Saya akan mengingatnya, ketika saya menemukannya. Untuk kode sensitif kinerja, saya tidak menggunakan properti lagi karena itu (memori otot pada saat ini).

Anda dapat mempertimbangkan untuk memanggil struct bersarang "Negara" daripada "Benih"?

@ellismg Tentu, terima kasih atas sarannya. Saya berjuang untuk menemukan nama yang bagus untuk struktur bagian dalam.

@karelz Saya pikir API ini akhirnya bagus untuk digunakan; kali ini, saya memeriksa untuk memastikan semuanya dikompilasi. Kecuali ada yang keberatan, saya akan mulai mengerjakan implementasi untuk ini.

@jamesqo @JonHanna mengapa kita membutuhkan Combine<T>(T obj) bukannya Combine(object o) ?

mengapa kita membutuhkan Combine(T obj) bukannya Gabungkan (objek o)?

Yang terakhir akan mengalokasikan jika instance adalah sebuah struct.

duh, terima kasih atas penjelasannya.

Kami tidak menyukai tipe bersarang karena tampaknya memperumit desain. Akar masalah adalah bahwa kita tidak dapat menamai statika dan non-statis dengan sama. Kami memiliki dua opsi: singkirkan statika atau ganti nama. Kami pikir mengganti nama menjadi Create paling masuk akal karena membuat kode yang cukup mudah dibaca, dibandingkan dengan menggunakan konstruktor default.

Kecuali ada penentangan yang kuat, itulah desain yang telah kami tetapkan:

```C#
Sistem ruang nama
{
struct publik HashCode : IEquatable
{
Buat HashCode statis publik (int hashCode);
Buat HashCode statis publik(T obj);
Buat HashCode statis publik(T obj, IEqualityComparerpembanding);

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

}
```

Mari kita tunggu beberapa hari untuk umpan balik tambahan untuk mengetahui apakah ada umpan balik yang kuat pada proposal yang disetujui. Kemudian kita bisa membuatnya 'untuk diperebutkan'.

Mengapa mempersulit desain? Saya dapat memahami betapa buruknya jika kita benar-benar harus menggunakan Kode HashCode.State dalam kode (misalnya untuk menentukan jenis variabel) tetapi apakah kita berharap hal itu sering terjadi? Sering kali saya akan mengembalikan Nilai secara langsung atau mengonversi ke int dan menyimpannya.

Saya pikir kombinasi Create dan Combine lebih buruk.

Silakan lihat https://github.com/dotnet/corefx/issues/8034#issuecomment -262661653

@terrajobst

Kami berpikir bahwa mengganti nama menjadi Create paling masuk akal karena membuat kode yang cukup mudah dibaca, dibandingkan dengan menggunakan konstruktor default.

Kecuali ada penentangan yang kuat, itulah desain yang telah kami tetapkan:

Saya mendengar Anda, tetapi saya memiliki pemikiran menit terakhir ketika saya sedang mengerjakan implementasi... bisakah kita menambahkan properti Zero / Empty statis ke HashCode , dan kemudian minta orang menelepon Combine dari sana? Itu akan membebaskan kita dari keharusan memiliki metode Combine / Create yang terpisah.

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

Adakah orang lain yang berpikir ini adalah ide yang bagus? (Sementara itu saya akan mengirimkan PR, dan jika orang berpikir demikian, saya akan mengubahnya di PR.)

@jamesqo , saya suka ide Kosong/Nol.

Saya akan baik-baik saja dengan itu (tidak ada preferensi yang kuat antara Empty vs. Create factory) ... @weshaggard @bartonjs @stephentoub @terrajobst bagaimana menurut kalian?

Saya pribadi berpikir Create() lebih baik; tapi saya lebih suka HashCode.Empty daripada new HashCode() .

Karena memungkinkan untuk versi yang tidak memiliki operator-baru, dan itu tidak menghalangi memutuskan nanti bahwa kita benar-benar ingin Buat sebagai bootstrap... ::mengangkat bahu::.

Itu sepenuhnya pushback saya (alias tidak terlalu banyak).

FWIW Saya akan memilih Create daripada Empty / Zero . Saya lebih suka memulai dengan nilai aktual daripada menggantung semuanya Empty / Zero . Itu hanya terasa/terlihat aneh.

Hal ini juga membuat orang enggan untuk menabur dengan nol, yang cenderung menjadi benih yang buruk.

Saya lebih suka Buat daripada Kosong. Ini sesuai dengan cara saya memikirkannya: Saya ingin membuat kode hash dan mencampur nilai tambahan. Saya akan baik-baik saja dengan pendekatan bersarang juga.

Sementara saya akan mengatakan bahwa menyebutnya Kosong bukanlah ide yang baik (dan itu sudah dikatakan), setelah seperti pemikiran ketiga saya masih berpikir itu bukan solusi yang buruk. Bagaimana dengan sesuatu seperti Builder. Meskipun masih memungkinkan untuk menggunakan nol, kata tersebut agak membuat Anda enggan untuk menggunakannya segera.

@JonHanna hanya untuk memperjelas: Anda bermaksud memilih Create , bukan?

Dan pada pemikiran keempat bagaimana dengan With daripada Create.

HashCode.Dengan(a).Gabungkan(b). Gabungkan (c)

Contoh penggunaan berdasarkan diskusi terbaru (dengan Create mungkin diganti dengan nama alternatif):

```c#
menimpa publik 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 Akan menyebabkan kode yang tidak konsisten + lebih banyak jitting, saya pikir b/c dari kombinasi yang lebih umum. Kami selalu dapat meninjau kembali ini di edisi lain jika ternyata diinginkan.

Untuk apa nilainya, saya lebih suka versi yang diusulkan semula, setidaknya dalam penggunaan (tidak yakin tentang komentar mengenai ukuran kode, jitting, dll.). Sepertinya berlebihan untuk memiliki struktur ekstra dan 10+ anggota berbeda untuk sesuatu yang dapat diekspresikan sebagai satu metode dengan beberapa kelebihan aritas yang berbeda. Saya juga bukan penggemar API gaya fasih secara umum, jadi mungkin itu mewarnai pendapat saya.

Saya tidak akan menyebutkan ini karena ini sedikit tidak biasa dan saya masih tidak yakin bagaimana perasaan saya tentang hal itu, tapi ini ide lain, hanya untuk memastikan semua alternatif telah dipertimbangkan...

Bagaimana jika kita melakukan sesuatu di sepanjang baris HashCodeCombiner "builder" ASP.NET Core yang dapat diubah, dengan metode Add serupa, tetapi juga menyertakan dukungan untuk sintaks penginisialisasi koleksi?

Penggunaan:

```c#
menimpa publik int GetHashCode() =>
Kode Hash baru { _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();
    }
}

Itu harus mengimplementasikan IEnumerable minimal bersama dengan setidaknya satu metode Add untuk mengaktifkan sintaks penginisialisasi koleksi. IEnumerable dapat diimplementasikan secara eksplisit untuk menyembunyikannya dari intellisense dan GetEnumerator dapat melempar NotSupportedException atau mengembalikan nilai kode hash sebagai satu item gabungan dalam enumerable, jika ada yang kebetulan menggunakannya (yang akan jarang terjadi).

@justinvp , Anda punya ide yang menarik. Namun, saya dengan hormat tidak setuju; Saya pikir HashCode harus tetap tidak berubah untuk menghindari gotcha dengan struct yang bisa berubah. Juga harus mengimplementasikan IEnumerable untuk ini tampaknya agak artifisial/terkelupas; jika seseorang memiliki direktif using System.Linq dalam file, maka Cast<> dan OfType<> akan muncul sebagai metode ekstensi jika mereka meletakkan titik di sebelah HashCode . Saya pikir kita harus tetap lebih dekat dengan proposal saat ini.

@jamesqo , saya setuju -- karenanya saya ragu untuk menyebutkannya. Satu-satunya hal yang saya sukai adalah penggunaannya bisa lebih bersih daripada chaining, tetapi itu sendiri merupakan kelemahan lain karena tidak jelas bahwa penginisialisasi koleksi bahkan dapat digunakan tanpa melihat penggunaan sampel.

@MadsTorgersen , @jaredpar , mengapa penginisialisasi koleksi memerlukan implementasi IEnumerable\komentar ketiga @ justinvp di atas.

@jamesqo , saya setuju lebih baik untuk menjaga ini tetap (dan tidak IEnumerable\

@mellinoe Saya pikir itu akan membuat kasus sederhana sedikit lebih sederhana, tetapi itu juga akan membuat apa pun di luar itu lebih rumit (dan kurang jelas tentang apa hal yang benar untuk dilakukan).

Itu termasuk:

  1. lebih banyak item daripada yang Anda miliki untuk kelebihan
  2. kondisi
  3. loop
  4. menggunakan pembanding

Pertimbangkan kode dari ASP.NET yang diposting sebelumnya tentang topik ini (diperbarui ke proposal saat ini):

```c#
var kode hash = kode hash
.Buat (Halaman Utama)
.Combine(ViewName, StringComparer.Ordinal)
.Combine(ControllerName, StringComparer.Ordinal)
.Combine(NamaArea, StringComparer.Ordinal);

jika (ViewLocationExpanderValues ​​!= null)
{
foreach (item var di ViewLocationExpanderValues)
{
kode hash = kode hash
.Combine(item.Key, StringComparer.Ordinal)
.Combine(item.Value, StringComparer.Ordinal);
}
}

kembali kode hash;

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;

Bahkan jika Anda mengabaikan pemanggilan GetHashCode() untuk pembanding khusus, saya merasa harus melewati nilai sebelumnya hashCode sebagai parameter pertama tidak langsung.

@KrzysztofCwalina Menurut catatan @ericlippert dalam Bahasa Pemrograman C# 1 , itu karena penginisialisasi koleksi (tidak mengejutkan) dimaksudkan sebagai gula sintaks untuk pembuatan koleksi, bukan untuk aritmatika (yang merupakan penggunaan umum lainnya dari metode bernama Add ).

1 Karena cara kerja Google Buku, tautan itu mungkin tidak berfungsi untuk semua orang.

@KrzysztofCwalina , dan perhatikan, ini membutuhkan IEnumerable non-generik, bukan IEnumerable<T> .

@svick , minor nit dalam contoh pertama Anda di atas: panggilan pertama ke .Combine akan menjadi .Create dengan proposal saat ini. Kecuali kita menggunakan pendekatan bersarang.

@svick

itu juga akan membuat apa pun di luar itu lebih rumit (dan kurang jelas tentang apa yang benar untuk dilakukan)

Entahlah, contoh kedua hampir tidak berbeda dari yang pertama secara keseluruhan, dan itu bukan IMO yang lebih kompleks. Dengan pendekatan kedua/asli, Anda cukup memasukkan banyak kode hash (saya pikir parameter pertama sebenarnya adalah IsMainPage.GetHashCode() ), jadi tampaknya mudah bagi saya. Tapi sepertinya saya minoritas di sini, jadi saya tidak akan memaksakan pendekatan aslinya. Saya tidak memiliki pendapat yang kuat; kedua contoh itu terlihat cukup masuk akal bagi saya.

@justinvp Terima kasih, diperbarui. (Saya pergi dengan proposal pertama di posting pertama, dan tidak menyadari itu sudah ketinggalan zaman, seseorang mungkin harus memperbaruinya.)

@mellinoe masalahnya sebenarnya yang kedua dapat menghasilkan bug halus. Ini adalah kode aktual dari salah satu proyek kami.

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

Kita hidup dengan itu, tetapi kita berurusan dengan hal-hal tingkat yang sangat rendah setiap hari; jadi bukan pengembang rata-rata yang pasti. Namun tidak sama di sini untuk menggabungkan v dengan w daripada w dengan v ... sama di antara v dan w menggabungkan. Kombinasi hash tidak komutatif, jadi merangkai satu demi satu sebenarnya dapat menghilangkan seluruh rangkaian kesalahan di tingkat API.

Saya pergi dengan proposal pertama di posting pertama, dan tidak menyadari itu kedaluwarsa, seseorang mungkin harus memperbaruinya.

Selesai.
BTW: Proposal ini sangat sulit untuk dilacak, terutama suara ... begitu banyak variasi (yang menurut saya bagus ;-))

@karelz Jika kita menambahkan Create API maka saya pikir kita masih bisa menambahkan Empty . Tidak harus satu atau yang lain, seperti yang dikatakan @bartonjs . Diajukan

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

@JonHanna

Hal ini juga membuat orang enggan untuk menabur dengan nol, yang cenderung menjadi benih yang buruk.

Algoritme hashing yang kami pilih akan sama dengan yang digunakan di HashHelpers hari ini, yang memiliki efek hash(0, x) == x . HashCode.Empty.Combine(x) akan menghasilkan hasil yang sama persis dengan HashCode.Create(x) , jadi secara objektif tidak ada perbedaan.

@jamesqo Anda lupa memasukkan Zero dalam proposal terakhir Anda. Jika itu kelalaian, dapatkah Anda memperbaruinya? Kami kemudian dapat meminta orang-orang untuk memilih proposal terbaru Anda. Sepertinya alternatif lain (lihat posting teratas yang saya perbarui) tidak banyak diikuti ...

@karelz Terima kasih telah melihat, diperbaiki.

@KrzysztofCwalina untuk memeriksa bahwa maksud Anda "Tambah" dalam arti menambahkan ke koleksi, bukan dalam arti lain. Saya tidak tahu apakah saya menyukai pembatasan ini, tetapi itulah yang kami putuskan saat itu.

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

Haruskah parameter diberi nama hashCode alih-alih hash karena nilai yang diteruskan akan menjadi kode hash yang kemungkinan diperoleh dari pemanggilan GetHashCode() ?

Empty / Zero

Jika kita akhirnya menyimpan ini, nama lain yang perlu dipertimbangkan adalah Default .

@justinvp

Haruskah parameter diberi nama hashCode alih-alih hash karena nilai yang diteruskan akan menjadi kode hash yang kemungkinan diperoleh dari memanggil GetHashCode()?

Saya ingin memberi nama parameter int hash dan parameter hashCode HashCode hashCode . Namun setelah dipikir-pikir, saya percaya bahwa hashCode akan lebih baik karena, seperti yang Anda sebutkan, hash agak kabur. Saya akan memperbarui API.

Jika kita akhirnya menyimpan ini, nama lain yang perlu dipertimbangkan adalah Default.

Ketika saya mendengar Default Saya pikir "cara biasa untuk melakukan sesuatu ketika Anda tidak tahu opsi mana yang harus dipilih," bukan "nilai default dari sebuah struct." misalnya Sesuatu seperti Encoding.Default memiliki konotasi yang sama sekali berbeda.

Algoritme hashing yang kami pilih akan sama dengan yang digunakan di HashHelpers hari ini, yang memiliki efek hash(0, x) == x. HashCode.Empty.Combine(x) akan menghasilkan hasil yang sama persis dengan HashCode.Create(x), jadi secara objektif tidak ada perbedaan.

Sebagai seseorang yang tidak tahu banyak tentang internal ini, saya sangat menyukai kesederhanaan HashCode.Create(x).Combine(...) . Create sangat jelas, karena digunakan di banyak tempat lain.

Jika Empty / Zero / Default tidak menyediakan penggunaan algoritmik apa pun, itu seharusnya tidak ada di sana IMO.

PS: thread yang sangat menarik!! Kerja bagus! 👍

@cwe1ss

Jika Empty / Zero / Default tidak memberikan penggunaan algoritmik apa pun, itu seharusnya tidak ada di sana IMO.

Memiliki bidang Empty memang menyediakan penggunaan algoritmik. Ini mewakili "nilai awal" dari mana Anda dapat menggabungkan hash. Misalnya, jika Anda ingin menggabungkan array hash secara ketat menggunakan Create , itu cukup menyakitkan:

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

Jika Anda memiliki Empty , itu menjadi jauh lebih alami:

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 Jenis ini cukup analog dengan ImmutableArray<T> untuk saya. Array kosong tidak terlalu berguna dengan sendirinya, tetapi sangat berguna sebagai "titik awal" untuk operasi lain, dan itulah sebabnya kami memiliki properti Empty untuknya. Saya pikir masuk akal untuk memilikinya untuk HashCode , juga; kami menyimpan Create .

@jamesqo Saya perhatikan bahwa Anda diam-diam/tidak sengaja mengubah nama arg obj menjadi value dalam proposal Anda https://github.com/dotnet/corefx/issues/8034#issuecomment -262661653. Saya mengubahnya kembali ke obj yang IMO menangkap lebih baik apa yang Anda dapatkan. Nama value lebih terkait dengan nilai hash "int" itu sendiri dalam konteks ini.
Saya terbuka untuk diskusi lebih lanjut tentang nama argumen jika diperlukan, tetapi mari kita ubah dengan sengaja dan lacak perbedaannya dengan proposal yang terakhir disetujui.

Saya telah memperbarui proposal di atas. Saya juga menyerukan perbedaan terhadap versi proposal yang terakhir disetujui.

Algoritme hashing yang kami pilih akan sama dengan yang digunakan di HashHelpers hari ini

Mengapa algoritma yang baik untuk dipilih sebagai salah satu yang harus digunakan di mana-mana? Asumsi apa yang akan dibuat tentang kode hash yang digabungkan? Jika digunakan di mana-mana, apakah akan membuka jalan baru untuk serangan DDoS? (Perhatikan bahwa kami telah dibakar oleh ini untuk hashing string di masa lalu.)

Bagaimana jika kita melakukan sesuatu di sepanjang baris "pembangun" HashCodeCombiner ASP.NET Core yang bisa berubah

Saya pikir ini adalah pola yang tepat untuk digunakan. Penggabung kode hash universal yang baik umumnya dapat menggunakan lebih banyak status daripada yang sesuai dengan kode hash itu sendiri, tetapi kemudian pola lancar rusak karena melewatkan struct yang lebih besar adalah masalah kinerja.

Mengapa algoritma yang baik untuk dipilih sebagai salah satu yang harus digunakan di mana-mana?

Seharusnya tidak digunakan di mana-mana. Lihat komentar saya di https://github.com/dotnet/corefx/issues/8034#issuecomment -260790829; itu terutama ditargetkan pada orang-orang yang tidak tahu banyak tentang hashing. Orang yang tahu apa yang mereka lakukan dapat mengevaluasinya untuk melihat apakah itu sesuai dengan kebutuhan mereka.

Asumsi apa yang akan dibuat tentang kode hash yang digabungkan? Jika digunakan di mana-mana, apakah akan membuka jalan baru untuk serangan DDoS?

Satu masalah dengan hash yang kami miliki saat ini adalah hash(0, x) == x . Jadi jika serangkaian nol atau nol diumpankan ke hash maka akan tetap 0. Lihat kode . Ini bukan untuk mengatakan bahwa nol tidak dihitung, tetapi tidak ada nol awal yang dihitung. Saya sedang mempertimbangkan untuk menggunakan sesuatu yang lebih kuat (tapi sedikit lebih mahal) seperti here , yang menambahkan konstanta ajaib untuk menghindari pemetaan nol ke nol.

Saya pikir ini adalah pola yang tepat untuk digunakan. Penggabung kode hash universal yang baik umumnya dapat menggunakan lebih banyak status daripada yang sesuai dengan kode hash itu sendiri, tetapi kemudian pola lancar rusak karena melewatkan struct yang lebih besar adalah masalah kinerja.

Saya tidak berpikir harus ada penggabung universal dengan ukuran struct besar yang mencoba menyesuaikan setiap kasus penggunaan. Alih-alih, saya membayangkan jenis kode hash terpisah yang semuanya berukuran int ( FnvHashCode , dll.) dan semuanya memiliki metode Combine mereka sendiri. Selain itu, tipe "pembangun" ini akan disimpan dalam metode yang sama, tidak diedarkan.

Saya tidak berpikir harus ada penggabung universal dengan ukuran struct besar yang mencoba menyesuaikan setiap kasus penggunaan.

Apakah ASP.NET Core akan dapat mengganti penggabung kode hash mereka sendiri - yang saat ini memiliki status 64-bit - dengan yang ini?

Saya membayangkan jenis kode hash terpisah yang semuanya berukuran int (FnvHashCode, dll.)

Bukankah ini mengarah pada ledakan kombinasi? Ini harus menjadi bagian dari proposal API untuk memperjelas tujuan desain API ini.

@jkotas Saya menyatakan keberatan serupa di awal diskusi. Berurusan dengan fungsi hash membutuhkan pengetahuan materi pelajaran. Tapi saya mengerti dan mendukung perbaikan masalah yang disebabkan pada tahun 2001 dengan pengenalan kode hash di akar kerangka dan tidak meresepkan resep untuk menggabungkan hash. Desain ini ditujukan untuk memecahkan 99% kasus (di mana tidak ada pengetahuan materi pelajaran yang tersedia atau bahkan diperlukan, karena sifat statistik hash cukup baik). ASP.Net Core harus dapat menggunakan sertakan penggabung tersebut ke dalam kerangka tujuan umum pada perakitan non-sistem seperti yang diusulkan untuk diskusi di sini: https://github.com/dotnet/corefx/issues/13757

Saya setuju bahwa itu adalah ide yang baik untuk memiliki penggabung kode hash yang mudah digunakan dalam 99% kasus. Namun, itu perlu memungkinkan lebih banyak status internal daripada hanya 32-bit.

BTW: ASP.NET menggunakan pola lancar untuk menggabungkan kode hash pada awalnya, tetapi berhenti melakukannya karena menyebabkan bug yang mudah dilewatkan: https://github.com/aspnet/Razor/pull/537

@jkotas tentang keamanan banjir hash.
PENOLAKAN: Bukan ahli (Anda harus berkonsultasi dengan satu dan MS memiliki lebih dari beberapa tentang masalah ini) .

Saya telah melihat-lihat dan meskipun bukan konsensus umum tentang masalah ini, ada argumen yang mendapatkan daya tarik saat ini. Kode hash berukuran 32 bit, saya memposting sebelum grafik yang menunjukkan kemungkinan tabrakan mengingat ukuran set. Itu berarti bahwa tidak peduli seberapa bagus algoritme Anda (melihat SipHash misalnya), cukup layak untuk menghasilkan banyak hash dan menemukan tabrakan dalam waktu yang wajar (berbicara sekitar kurang dari satu jam). Masalah-masalah itu perlu diatasi pada struktur data yang memegang hash, mereka tidak dapat diselesaikan pada tingkat fungsi hash. Membayar kinerja ekstra pada non-kriptografi untuk mengamankan dari hash-flooding tanpa memperbaiki struktur data yang mendasarinya tidak akan menyelesaikan masalah.

EDIT: Anda memposting saat saya sedang menulis. Berdasarkan hal ini, apa keuntungan status 64bit untuk Anda?

@jkotas Saya melihat masalah yang Anda

Reaksi terhadap aspnet/Umum#40

Deskripsi https://github.com/aspnet/Common/issues/40 :

Temukan bugnya:

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

Ayo. Argumen itu seperti mengatakan string harus bisa berubah karena orang tidak menyadari Substring mengembalikan string baru. Struct yang bisa berubah jauh lebih buruk dalam hal gotcha; Saya pikir kita harus menjaga agar struct tidak berubah.

mengenai keamanan hash-flooding.

Ada dua sisi dari ini: desain konstruksi yang benar (struktur data yang kuat, dll.); dan mitigasi masalah dalam desain yang ada. Keduanya penting.

@karelz Mengenai penamaan parameter

Saya perhatikan bahwa Anda diam-diam/tidak sengaja mengubah nama arg obj menjadi nilai dalam proposal Anda dotnet/corefx#8034 (komentar). Saya mengubahnya kembali ke obj yang IMO menangkap lebih baik apa yang Anda dapatkan. Nilai nama lebih terkait dengan nilai hash "int" itu sendiri dalam konteks ini.
Saya terbuka untuk diskusi lebih lanjut tentang nama argumen jika diperlukan, tetapi mari kita ubah dengan sengaja dan lacak perbedaannya dengan proposal yang terakhir disetujui.

Saya sedang mempertimbangkan, dalam proposal mendatang, untuk menambahkan API guna menggabungkan nilai secara massal. Misalnya: CombineRange(ReadOnlySpan<T>) . Jika kita menamai ini obj , kita harus memberi nama parameter di sana objs , yang terdengar sangat canggung. Jadi kita harus menamainya item sebagai gantinya; di masa depan, kita dapat menamai parameter span items . Memperbarui proposal.

@jkota setuju, tetapi intinya di sini adalah kami tidak mengurangi apa pun di tingkat penggabung...

Satu-satunya hal yang dapat kita lakukan adalah memiliki seed acak, yang untuk semua status dan tujuan saya ingat pernah melihat kode di string dan itu diperbaiki per build. (bisa jadi salah tentang itu, karena itu sudah lama sekali). Memiliki implementasi benih acak yang tepat adalah satu-satunya mitigasi yang dapat diterapkan di sini.

Ini adalah tantangan, beri saya string terbaik Anda dan atau fungsi hash memori dengan seed acak tetap dan saya akan pergi dan membangun satu set pada kode hash 32 bit yang hanya akan menghasilkan tabrakan. Saya tidak takut untuk mengeluarkan tantangan seperti itu karena cukup mudah dilakukan, teori probabilitas ada di pihak saya. Saya bahkan akan pergi dan membuat taruhan, tetapi saya tahu saya akan memenangkannya, jadi pada dasarnya ini bukan taruhan lagi.

Selain itu... analisis yang lebih dalam menunjukkan bahwa bahkan jika mitigasi adalah kemampuan untuk memiliki "bibit acak" bawaan per proses, penggabung yang lebih berbelit-belit tidak diperlukan. Karena pada dasarnya Anda mengurangi masalah di sumbernya.

Katakanlah Anda memiliki M1 dan M2 dengan benih acak yang berbeda rs1 dan rs2 ....
M1 akan mengeluarkan h1 = hash('a', rs1) dan h2=hash('b', rs1)
M2 akan mengeluarkan h1' = hash('a', rs2) dan h2'=hash('b', rs2)
Poin kuncinya di sini adalah bahwa h1 dan h1' akan berbeda dengan probabilitas 1/ (int.MaxInt-1) (jika hash cukup baik) yang untuk semua tujuan adalah sebagai baik seperti yang akan didapat.
Oleh karena itu, apa pun c(x,y) Anda putuskan untuk digunakan (jika cukup baik) sudah memperhitungkan mitigasi bawaan di sumbernya.

EDIT: Saya menemukan kodenya, Anda menggunakan Marvin32 yang berubah di setiap domain sekarang. Jadi mitigasi untuk string adalah menggunakan biji acak per run. Yang seperti yang saya nyatakan adalah mitigasi yang cukup baik.

@jkotas

Apakah ASP.NET Core akan dapat mengganti penggabung kode hash mereka sendiri - yang saat ini memiliki status 64-bit - dengan yang ini?

Sangat; ia menggunakan algoritma hashing yang sama. Saya baru saja membuat aplikasi pengujian ini untuk mengukur jumlah tabrakan dan menjalankannya 10 kali. Tidak ada perbedaan yang signifikan dari penggunaan 64 bit.

Saya membayangkan jenis kode hash terpisah yang semuanya berukuran int (FnvHashCode, dll.)

Bukankah ini mengarah pada ledakan kombinasi? Ini harus menjadi bagian dari proposal API untuk memperjelas tujuan desain API ini.

@jkotas , tidak akan. Desain kelas ini tidak akan mengatur desain untuk API hashing di masa mendatang. Itu harus dianggap sebagai skenario yang lebih maju, harus masuk dalam proposal yang berbeda seperti dotnet/corefx#13757, dan akan memiliki diskusi desain yang berbeda. Saya percaya jauh lebih penting untuk memiliki API sederhana untuk algoritma hashing umum, untuk pemula yang berjuang dengan mengesampingkan GetHashCode .

Saya setuju bahwa itu adalah ide yang baik untuk memiliki penggabung kode hash yang mudah digunakan dalam 99% kasus. Namun, itu perlu memungkinkan lebih banyak status internal daripada hanya 32-bit.

Kapan kita membutuhkan lebih banyak status internal daripada 32 bit? edit: Jika memungkinkan orang untuk memasukkan logika hashing khusus, saya pikir (sekali lagi) itu harus dianggap sebagai skenario lanjutan dan dibahas di dotnet/corefx#13757.

Anda menggunakan Marvin32 yang berubah di setiap domain sekarang

Benar, mitigasi pengacakan kode hash string diaktifkan secara default di .NET Core. Ini tidak diaktifkan secara default untuk aplikasi mandiri dalam .NET Framework penuh karena kompatibilitas; itu hanya diaktifkan melalui kebiasaan (misalnya di lingkungan berisiko tinggi).

Kami masih memiliki kode untuk hashing non-acak di .NET Core, tetapi tidak apa-apa untuk menghapusnya. Saya tidak berharap bahwa kita akan membutuhkannya lagi. Itu juga akan membuat perhitungan kode hash string sedikit lebih cepat karena tidak akan ada pemeriksaan apakah akan menggunakan jalur non-acak lagi.

Algoritme Marvin32 yang digunakan untuk menghitung kode hash string acak memiliki status internal 64-bit. Itu dipilih oleh para ahli mata pelajaran MS. Saya cukup yakin bahwa mereka memiliki alasan yang baik untuk menggunakan status internal 64-bit, dan mereka tidak menggunakannya hanya untuk membuat segalanya lebih lambat.

Penggabung hash tujuan umum harus terus mengembangkan mitigasi ini: Ia harus menggunakan benih acak dan algoritme penggabungan kode hash yang cukup kuat. Idealnya, itu akan menggunakan Marvin32 yang sama dengan hashing string acak.

Algoritme Marvin32 yang digunakan untuk menghitung kode hash string acak memiliki status internal 64-bit. Itu dipilih oleh para ahli mata pelajaran MS. Saya cukup yakin bahwa mereka memiliki alasan yang baik untuk menggunakan status internal 64-bit, dan mereka tidak menggunakannya hanya untuk membuat segalanya lebih lambat.

@jkotas , penggabung kode hash yang Anda tautkan tidak menggunakan Marvin32. Ini menggunakan algoritma DJBx33x naif yang sama yang digunakan oleh string.GetHashCode non-acak.

Penggabung hash tujuan umum harus terus mengembangkan mitigasi ini: Ia harus menggunakan benih acak dan algoritme penggabungan kode hash yang cukup kuat. Idealnya, itu akan menggunakan Marvin32 yang sama dengan hashing string acak.

Jenis ini tidak dimaksudkan untuk digunakan di tempat yang rentan terhadap serangan hash DoS. Ini ditargetkan untuk orang-orang yang tidak tahu lebih baik untuk menambahkan/xor, dan akan membantu mencegah hal-hal seperti https://github.com/dotnet/coreclr/pull/4654.

Penggabung hash tujuan umum harus terus mengembangkan mitigasi ini: Ia harus menggunakan benih acak dan algoritme penggabungan kode hash yang cukup kuat. Idealnya, itu akan menggunakan Marvin32 yang sama dengan hashing string acak.

Kemudian kita harus berbicara dengan tim C# agar mereka menerapkan algoritma hashing ValueTuple dimitigasi. Karena kode itu juga akan digunakan di lingkungan berisiko tinggi. Dan tentu saja Tuple https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Tuple.cs#L60 atau System.Numerics.HashHelpers (digunakan di seluruh tempat).

Sekarang, sebelum kita memutuskan bagaimana mengimplementasikannya, saya akan melihat ke ahli subjek yang sama jika membayar biaya algoritma penggabungan kode hash yang diacak sepenuhnya sepadan (jika ada tentu saja) meskipun itu tidak akan mengubah cara API dirancang baik (di bawah API yang diusulkan Anda dapat menggunakan status 512bit dan masih memiliki API publik yang sama, jika Anda bersedia membayar biayanya, tentu saja).

Ini ditargetkan untuk orang-orang yang tidak tahu lebih baik untuk menambahkan/xor

Itulah mengapa penting untuk menjadi kuat. Nilai kunci dari .NET adalah bahwa ia mengatasi masalah bagi orang-orang yang tidak tahu lebih baik.

Dan sementara kita melakukannya, jangan lupakan IntPtr https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/IntPtr.cs#L119
Yang itu sangat jahat, xor mungkin yang terburuk di sana karena bad akan bertabrakan dengan dab .

menerapkan algoritma hashing ValueTuple dimitigasi

Poin bagus. Saya tidak yakin apakah ValueTuple dikirimkan atau apakah ini masih waktu untuk melakukan ini. Dibuka https://github.com/dotnet/corefx/issues/14046.

jangan lupa tentang IntPtr

Ini adalah kesalahan masa lalu ... standar untuk memperbaikinya jauh lebih tinggi.

@jkotas

Ini adalah kesalahan masa lalu ... standar untuk memperbaikinya jauh lebih tinggi.

Saya pikir salah satu poin dari .Net Core adalah bahwa standar untuk perubahan "kecil" seperti itu seharusnya jauh lebih rendah. Jika seseorang bergantung pada implementasi IntPtr.GetHashCode (yang seharusnya tidak), mereka dapat memilih untuk tidak mengupgrade versi .Net Core mereka.

bilah untuk perubahan "kecil" seperti itu seharusnya jauh lebih rendah

Ya, itu - dibandingkan dengan .NET Framework penuh. Tetapi Anda masih harus melakukan pekerjaan untuk mendorong perubahan melalui sistem dan Anda mungkin menemukan bahwa itu tidak sebanding dengan rasa sakitnya. Contoh terbaru adalah perubahan pada algoritma hashing Tuple<T> yang dikembalikan karena rusak F#: https://github.com/dotnet/coreclr/pull/6767#issuecomment -256896016

@jkotas

Jika kita membuat HashCode 64-bit, apakah menurut Anda desain yang tidak dapat diubah akan mematikan kinerja di lingkungan 32-bit? Saya setuju dengan pembaca lain, pola pembangun tampaknya jauh lebih buruk.

Bunuh kinerjanya - tidak. Penalti kinerja dibayar untuk gula sintaks - ya.

Penalti kinerja dibayar untuk gula sintaks - ya.

Apakah ini sesuatu yang dapat dioptimalkan oleh JIT di masa depan?

Membunuh kinerja - tidak.
Penalti kinerja dibayar untuk gula sintaks - ya.

Ini lebih dari gula sintaksis. Jika kita ingin membuat HashCode sebuah kelas, maka itu akan menjadi gula sintaksis. Tetapi tipe nilai yang bisa berubah adalah peternakan serangga.

Mengutip Anda dari sebelumnya:

Itulah mengapa penting untuk menjadi kuat. Nilai kunci dari .NET adalah bahwa ia mengatasi masalah bagi orang-orang yang tidak tahu lebih baik.

Saya berpendapat bahwa tipe nilai yang bisa berubah bukanlah API yang kuat untuk sebagian besar orang yang tidak tahu lebih baik.

Saya berpendapat bahwa tipe nilai yang bisa berubah bukanlah API yang kuat untuk sebagian besar orang yang tidak tahu lebih baik.

Setuju. Pikir, saya pikir sangat disayangkan bahwa itu adalah kasus untuk tipe pembangun struct yang bisa berubah. Saya menggunakan mereka semua yang waktu karena mereka bagus dan ketat. [MustNotCopy] penjelasan siapa saja?

MustNotCopy adalah impian pecinta struct yang menjadi kenyataan. @jaredpar?

MustNotCopy hanya seperti tumpukan tetapi lebih sulit untuk digunakan

Saya sarankan jangan membuat kelas apa pun tetapi buat metode ekstensi untuk menggabungkan hash

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

Itu semuanya! Ini cepat dan mudah digunakan.

@AlexRadch Saya tidak suka itu mencemari daftar metode untuk semua bilangan bulat , bukan hanya yang dimaksudkan sebagai hash.

Juga, Anda memiliki metode yang melanjutkan rantai komputasi kode hash, tetapi bagaimana Anda memulainya? Apakah Anda harus melakukan sesuatu yang tidak jelas seperti memulai dari nol? Yaitu 0.CombineHash(this.FirstName).CombineHash(this.LastName) .

Pembaruan: Per komentar di dotnet/corefx#14046, diputuskan bahwa rumus hash yang ada akan disimpan untuk ValueTuple :

@jamesqo Terima kasih atas bantuannya.
Dari diskusi terakhir dengan @jkotas dan @VSadov , kami
Melakukan pengacakan menjaga pintu untuk mengubah fungsi hash di masa mendatang jika diperlukan.

@jkotas , bisakah kita menyimpan hash berbasis 5 ROL saat ini untuk HashCode lalu, dan mengecilkannya kembali menjadi 4 byte? Ini akan menghilangkan semua masalah dengan penyalinan struct. Kita dapat membuat HashCode.Empty mewakili nilai hash acak.

@svick
Ya ini mencemari metode untuk semua bilangan bulat, tetapi dapat ditempatkan di ruang nama yang terpisah dan jika Anda tidak bekerja dengan hash, Anda tidak akan memasukkannya dan tidak akan melihatnya.

0.CombineHash(this.FirstName).CombineHash(this.LastName) harus ditulis sebagai this.FirstName.GetHash().CombineHash(this.LastName)

Untuk mengimplementasikan mulai dari seed, ia dapat memiliki metode statis berikutnya

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

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

Jadi setiap kelas akan memiliki benih yang berbeda untuk pengacakan hash.

@jkotas , bisakah kita menyimpan hash berbasis ROL 5 saat ini untuk HashCode, dan mengecilkannya kembali menjadi 4 byte?

Saya pikir pembantu pembangunan kode hash platform publik perlu menggunakan status 64-bit agar kuat. Jika hanya 32-bit, itu akan cenderung menghasilkan hasil yang buruk ketika digunakan untuk hash lebih banyak elemen, array atau koleksi pada khususnya. Bagaimana Anda menulis dokumentasi tentang kapan sebaiknya digunakan vs. tidak? Ya, itu adalah instruksi tambahan yang dihabiskan untuk mencampur bit, tapi saya pikir itu tidak penting. Instruksi semacam ini dijalankan dengan sangat cepat. Pengalaman saya adalah lebih baik melakukan lebih banyak pencampuran daripada lebih sedikit karena efek melakukan terlalu sedikit pencampuran jauh lebih parah daripada melakukan terlalu banyak.

Juga, saya masih memiliki kekhawatiran tentang bentuk API yang diusulkan. Saya percaya bahwa masalahnya harus dianggap sebagai pembuatan kode hash, bukan penggabungan kode hash. Mungkin terlalu dini untuk menambahkan ini sebagai platform API, dan kita sebaiknya menunggu dan melihat apakah pola yang lebih baik muncul untuk ini. Ini tidak mencegah seseorang menerbitkan paket nuget (sumber) dengan API ini, atau corefx menggunakannya sebagai pembantu internal.

@jkotas memiliki status 64bit tidak menjamin bahwa output Anda akan memiliki properti statistik yang tepat, fungsi penggabungan itu sendiri harus dirancang untuk menggunakan status 64bit internal. Juga, jika fungsi penggabungannya bagus (secara statistik), tidak ada yang namanya pencampuran lebih dari sedikit. Jika hashing memiliki pengacakan, longsoran salju dan sifat statistik lainnya yang menarik, pencampuran diperhitungkan karena secara teknis merupakan fungsi hash yang dibuat khusus.

Lihat apa yang membuat fungsi hash bagus (yang beberapa jelas seperti xor : http://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and -kecepatan dan https://research.neustar.biz/2012/02/02/choosing-a-good-hash-function-part-3/

@jamesqo BTW, saya baru menyadari bahwa penggabung tidak akan berfungsi untuk kasus: "Saya sebenarnya menggabungkan hash (bukan hash runtime) karena benih akan berubah setiap saat." ... konstruktor publik dengan seed?

@jkotas

Saya pikir pembantu pembangunan kode hash platform publik perlu menggunakan status 64-bit agar kuat. Jika hanya 32-bit, itu akan cenderung menghasilkan hasil yang buruk ketika digunakan untuk hash lebih banyak elemen, array atau koleksi pada khususnya.

Apakah ini penting ketika akhirnya akan diringkas menjadi satu int pada akhirnya?

@jamesqo Tidak juga, ukuran status hanya bergantung pada fungsinya, bukan pada ketahanannya. Sebenarnya Anda benar-benar dapat membuat fungsi hash Anda lebih buruk jika kombinasi tidak dirancang untuk bekerja seperti itu, dan paling baik Anda membuang-buang sumber daya karena Anda tidak dapat memperoleh keacakan dari paksaan.

Akibat wajar: jika Anda bekerja sama, pastikan bahwa fungsinya sangat baik secara statistik atau Anda hampir dijamin akan membuatnya lebih buruk.

Hal ini tergantung pada apakah ada korelasi antara item. Jika tidak ada korelasi, status 32-bit dan rotl sederhana (atau bahkan xor) berfungsi dengan baik. Jika ada korelasi, itu tergantung.

Pertimbangkan jika seseorang menggunakan ini untuk membuat kode hash string dari karakter individual. Bukannya kemungkinan seseorang akan benar-benar melakukan ini untuk string, tetapi ini menunjukkan masalahnya:

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

Ini akan memberikan hasil yang buruk untuk string dengan status 32-bit dan rotl sederhana karena karakter dalam string dunia nyata cenderung berkorelasi. Seberapa sering item yang digunakan untuk berkorelasi, dan seberapa buruk hasil yang akan diberikan? Sulit untuk diceritakan, meskipun hal-hal dalam kehidupan nyata cenderung berkorelasi dengan cara yang tidak terduga.

Akan sangat bagus untuk menambahkan metode selanjutnya ke API yang mendukung pengacakan Hash.

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

@jkotas Saya belum mengujinya, jadi saya percaya Anda melakukannya. Tapi itu pasti mengatakan sesuatu tentang fungsi yang ingin kita gunakan. Ini tidak cukup baik setidaknya jika Anda ingin menukar kecepatan untuk keandalan (tidak ada yang bisa melakukan hal bodoh dengannya). Saya sekali kali menyukai desain bahwa ini bukan fungsi hashing non-crypto, tetapi cara cepat untuk menggabungkan kode hash yang tidak berkorelasi (yang acak seperti yang mereka dapatkan).

Jika ingin yang kami tuju adalah bahwa tidak ada yang akan melakukan hal-hal bodoh dengannya, menggunakan status 64bit tidak memperbaiki apa pun, kami hanya menyembunyikan masalahnya. Masih dimungkinkan untuk membuat input yang akan mengeksploitasi korelasi itu. Yang mengarahkan kita lagi ke argumen yang sama yang saya buat 18 hari yang lalu. Lihat: https://github.com/dotnet/corefx/issues/8034#issuecomment -261301533

Saya sekali lagi menyukai desain bahwa ini bukan fungsi hashing non-crypto, tetapi cara cepat untuk menggabungkan kode hash yang tidak berkorelasi

Cara tercepat untuk menggabungkan kode hash yang tidak berkorelasi adalah xor...

Benar, tetapi kami tahu bahwa terakhir kali tidak bekerja dengan baik (IntPtr muncul di benak saya). Rotasi dan XOR (saat ini) sama cepatnya, tanpa kehilangan jika seseorang memasukkan hal-hal yang berkorelasi.

Tambahkan pengacakan kode hash dengan public static HashCode CreateRandomized(Type type); atau dengan metode public static HashCode CreateRandomized<T>(); atau dengan keduanya.

@jkotas Saya pikir saya mungkin telah menemukan pola yang lebih baik untuk ini. Bagaimana jika kita menggunakan pengembalian ref C# 7? Alih-alih mengembalikan HashCode setiap kali, kami akan mengembalikan ref HashCode yang sesuai dengan register.

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

Penggunaan tetap sama seperti sebelumnya:

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

Satu-satunya downside adalah bahwa kita kembali ke struct yang bisa berubah lagi. Tapi saya tidak berpikir ada cara untuk tidak menyalin dan tidak berubah pada saat yang bersamaan.

( ref this belum berfungsi, tetapi saya melihat PR di Roslyn untuk mengaktifkannya di sini. )


@AlexRadch Saya rasa tidak bijaksana untuk menggabungkan hash lebih banyak dengan tipenya, karena mendapatkan kode hash dari tipe itu mahal.

@jamesqo public static HashCode CreateRandomized<T>(); tidak mendapatkan kode hash ketik. Ini menciptakan HashCode acak untuk jenis ini.

@jamesqo " ref this belum bekerja". Bahkan setelah masalah Roslyn diperbaiki, ref this tidak akan tersedia untuk repo corefx untuk sementara waktu (saya tidak yakin berapa lama, @stephentoub mungkin dapat menetapkan harapan).

Diskusi desain tidak konvergen di sini. Apalagi 200 komentar sangat sulit untuk diikuti.
Kami berencana untuk mengambil @jkotas minggu depan dan

Di samping: Saya menyarankan untuk menutup masalah ini dan membuat yang baru dengan "proposal yang diberkati" ketika kita memilikinya minggu depan untuk mengurangi beban mengikuti diskusi panjang. Beri tahu saya jika menurut Anda itu ide yang buruk.

@jcouv Saya baik-baik saja dengan itu belum berfungsi, jadi selama kita bisa mengikuti desain ini saat dirilis. (Saya juga berpikir mungkin untuk mengatasi ini sementara menggunakan Unsafe .)

@karelz OK :smile: Saya akan menutup proposal ini nanti ketika saya punya waktu, dan membuka yang baru. Saya setuju; browser saya tidak dapat menangani 200+ komentar dengan baik.

@karelz saya menemukan halangan; ternyata PR yang dimaksud mencoba mengaktifkan pengembalian ref this untuk tipe referensi sebagai lawan dari tipe nilai. ref this tidak dapat dikembalikan dengan aman dari struct; lihat di sini untuk alasannya. Jadi kompromi pengembalian kembali tidak akan berhasil.

Bagaimanapun, saya akan menutup masalah ini. Saya telah membuka masalah lain di sini: https://github.com/dotnet/corefx/issues/14354

Harus dapat mengembalikan ref "ini" dari posting metode ekstensi tipe nilai https://github.com/dotnet/roslyn/pull/15650 meskipun saya menganggap C#vNext...

@benaadams

Harus dapat mengembalikan ref "ini" dari metode ekstensi tipe nilai posting dotnet/roslyn#15650 meskipun saya menganggap C#vNext...

Benar. Dimungkinkan untuk mengembalikan this dari metode ekstensi ref this . Tidak mungkin mengembalikan this dari metode instance struct normal. Ada banyak detail seumur hidup berdarah tentang mengapa itu terjadi :(

@redknightlois

jika kita ingin menjadi ketat, satu-satunya hash harus uint , itu dapat dianggap sebagai pengawasan bahwa kerangka kerja mengembalikan int bawah cahaya itu.

Kepatuhan CLS? Bilangan bulat yang tidak ditandatangani tidak sesuai dengan CLS.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

v0l picture v0l  ·  3Komentar

yahorsi picture yahorsi  ·  3Komentar

chunseoklee picture chunseoklee  ·  3Komentar

nalywa picture nalywa  ·  3Komentar

Timovzl picture Timovzl  ·  3Komentar