Tại sao điều quan trọng là ghi đè GetHashCode khi phương thức Equals bị ghi đè?


1445

Cho lớp sau

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        if (fooItem == null) 
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

Tôi đã ghi đè Equalsphương thức vì Foobiểu diễn một hàng cho Foobảng s. Đó là phương pháp ưa thích để ghi đè GetHashCode?

Tại sao nó quan trọng để ghi đè GetHashCode?


36
Điều quan trọng là phải thực hiện cả bằng và gethashcode, do va chạm, đặc biệt là trong khi sử dụng từ điển. nếu hai đối tượng trả về cùng một mã băm, chúng được chèn vào từ điển với chuỗi. Trong khi truy cập các mục bằng phương pháp được sử dụng.
DarthVader

Câu trả lời:


1319

Có, điều quan trọng là nếu mục của bạn sẽ được sử dụng làm khóa trong từ điển, hoặc HashSet<T>, v.v. - vì mục này được sử dụng (trong trường hợp không có tùy chỉnh IEqualityComparer<T>) để nhóm các mục vào nhóm. Nếu hash-mã cho hai mục không phù hợp, họ có thể không bao giờ được coi là bằng nhau ( Equals sẽ chỉ đơn giản là không bao giờ được gọi là).

Phương thức GetHashCode () sẽ phản ánh Equalslogic; các quy tắc là:

  • nếu hai thứ bằng nhau ( Equals(...) == true) thì chúng phải trả về cùng một giá trị choGetHashCode()
  • nếu GetHashCode()bằng nhau, thì không cần thiết phải giống nhau; đây là một sự va chạm, và Equalssẽ được gọi để xem đó có phải là một sự bình đẳng thực sự hay không.

Trong trường hợp này, có vẻ như " return FooId;" là một GetHashCode()triển khai phù hợp . Nếu bạn đang kiểm tra nhiều thuộc tính, thông thường kết hợp chúng bằng cách sử dụng mã như dưới đây, để giảm va chạm chéo (nghĩa là new Foo(3,5)có mã băm khác nhau new Foo(5,3)):

unchecked // only needed if you're compiling with arithmetic checks enabled
{ // (the default compiler behaviour is *disabled*, so most folks won't need this)
    int hash = 13;
    hash = (hash * 7) + field1.GetHashCode();
    hash = (hash * 7) + field2.GetHashCode();
    ...
    return hash;
}

Ồ - để thuận tiện, bạn cũng có thể xem xét việc cung cấp ==!=vận hành khi ghi đè EqualsGetHashCode.


Một minh chứng về những gì xảy ra khi bạn nhận được điều này là ở đây .


49
Tôi có thể hỏi ahy bạn đang nhân lên với các yếu tố như vậy?
Leandro López

22
Thật ra, tôi có thể mất một trong số họ; vấn đề là cố gắng giảm thiểu số lượng va chạm - để một đối tượng {1,0,0} có hàm băm khác với {0,1,0} và {0,0,1} (nếu bạn hiểu ý tôi là gì ),
Marc Gravell

13
Tôi đã điều chỉnh các con số để làm cho nó rõ ràng hơn (và thêm một hạt giống). Một số mã sử dụng các số khác nhau - ví dụ: trình biên dịch C # (đối với các loại ẩn danh) sử dụng hạt giống 0x51ed270b và hệ số -521134295.
Marc Gravell

76
@Leandro López: Thông thường các yếu tố được chọn là số nguyên tố vì nó làm cho số lượng va chạm nhỏ hơn.
Andrei Rînea

29
"Ồ - để thuận tiện, bạn cũng có thể cân nhắc việc cung cấp các toán tử == và! = Khi ghi đè Equals và GethashCode.": Microsoft không khuyến khích toán tử triển khai == cho các đối tượng không thể thay đổi - msdn.microsoft.com/en-us/l Library / ms173147.aspx - "Không nên ghi đè toán tử == trong các loại không thay đổi."
antiduh

137

Thật sự rất khó để thực hiện GetHashCode()chính xác bởi vì, ngoài các quy tắc mà Marc đã đề cập, mã băm không nên thay đổi trong suốt vòng đời của một đối tượng. Do đó, các trường được sử dụng để tính mã băm phải là bất biến.

Cuối cùng tôi đã tìm thấy một giải pháp cho vấn đề này khi tôi làm việc với NHibernate. Cách tiếp cận của tôi là tính mã băm từ ID của đối tượng. ID chỉ có thể được đặt mặc dù là hàm tạo nên nếu bạn muốn thay đổi ID, điều này rất khó xảy ra, bạn phải tạo một đối tượng mới có ID mới và do đó là mã băm mới. Cách tiếp cận này hoạt động tốt nhất với GUID vì bạn có thể cung cấp hàm tạo không tham số tạo ngẫu nhiên ID.


20
@vanja. Tôi tin rằng nó phải làm với: nếu bạn thêm đối tượng vào từ điển và sau đó thay đổi id của đối tượng, khi tìm nạp sau bạn sẽ sử dụng một hàm băm khác để lấy nó vì vậy bạn sẽ không bao giờ lấy được nó từ từ điển.
ANeves

74
Tài liệu của Microsoft về hàm GetHashCode () không nói rõ cũng không ngụ ý rằng hàm băm đối tượng phải duy trì nhất quán trong suốt vòng đời của nó. Trong thực tế, nó giải thích cụ thể một trường hợp cho phép trong đó có thể không : "Phương thức GetHashCode cho một đối tượng phải luôn trả về cùng mã băm miễn là không có sửa đổi nào cho trạng thái đối tượng xác định giá trị trả về của phương thức Equals của đối tượng . "
Peter ALLenWebb

37
"Mã băm không nên thay đổi trong suốt vòng đời của một đối tượng" - điều đó không đúng.
ngày tận thế

7
Một cách tốt hơn để nói đó là "mã băm (cũng không phải là sự biến đổi của bằng) nên thay đổi trong khoảng thời gian đối tượng được sử dụng làm khóa cho bộ sưu tập" Vì vậy, nếu bạn thêm đối tượng vào từ điển làm khóa, bạn phải đảm bảo rằng GetHashCode và Equals sẽ không thay đổi đầu ra của chúng cho một đầu vào nhất định cho đến khi bạn xóa đối tượng khỏi từ điển.
Scott Chamberlain

11
@ScottChamberlain Tôi nghĩ rằng bạn đã quên KHÔNG trong bình luận của mình, nên là: "mã băm (cũng không phải là sự thay đổi của bằng) KHÔNG nên thay đổi trong khoảng thời gian đối tượng được sử dụng làm khóa cho bộ sưu tập". Đúng?
Stan Prokop

57

Bằng cách ghi đè Bằng, về cơ bản, bạn nói rằng bạn là người hiểu rõ hơn về cách so sánh hai trường hợp của một loại nhất định, do đó bạn có thể là ứng cử viên tốt nhất để cung cấp mã băm tốt nhất.

Đây là một ví dụ về cách ReSharper viết hàm GetHashCode () cho bạn:

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

Như bạn có thể thấy, nó chỉ cố gắng đoán mã băm tốt dựa trên tất cả các trường trong lớp, nhưng vì bạn biết phạm vi giá trị hoặc miền của đối tượng của bạn, bạn vẫn có thể cung cấp mã tốt hơn.


7
Điều này sẽ luôn luôn trả về không? Có lẽ nên khởi tạo kết quả thành 1! Cũng cần thêm một vài dấu chấm phẩy.
Sam Mackrill

16
Bạn có biết toán tử XOR (^) làm gì không?
Stephen Drew

1
Như tôi đã nói, đây là những gì R # viết cho bạn (ít nhất đó là những gì nó đã làm vào năm 2008) khi được yêu cầu. Rõ ràng, đoạn mã này được dự định sẽ được điều chỉnh bởi các lập trình viên theo một cách nào đó. Đối với các dấu chấm phẩy bị thiếu ... vâng, có vẻ như tôi đã bỏ chúng khi tôi sao chép mã từ một vùng chọn trong Visual Studio. Tôi cũng nghĩ mọi người sẽ tìm ra cả hai.
Bẫy

3
@SamMackrill Tôi đã thêm vào dấu chấm phẩy bị thiếu.
Matthew Murdoch

5
@SamMackrill Không, nó sẽ không trả về 0. 0 ^ a = a, vì vậy 0 ^ m_someVar1 = m_someVar1. Ông cũng có thể thiết lập giá trị ban đầu của resultđể m_someVar1.
Millie Smith

41

Xin đừng quên kiểm tra tham số obj nullkhi ghi đè Equals(). Và cũng so sánh các loại.

public override bool Equals(object obj)
{
    Foo fooItem = obj as Foo;

    if (fooItem == null)
    {
       return false;
    }

    return fooItem.FooId == this.FooId;
}

Lý do cho điều này là: Equalsphải trả về sai khi so sánh với null. Xem thêm http://msdn.microsoft.com/en-us/l Library / bsc2ak47.aspx


6
Việc kiểm tra loại này sẽ thất bại trong trường hợp một lớp con đề cập đến phương thức Siêu lớp bằng như một phần so sánh của chính nó (ví dụ: base.Equals (obj)) - nên sử dụng thay thế
sweetfa

@sweetfa: Nó phụ thuộc vào cách phương thức Equals của lớp con được triển khai. Nó cũng có thể gọi base.Equals ((BaseType) obj)) sẽ hoạt động tốt.
huha

2
Không, nó sẽ không: msdn.microsoft.com/en-us/l Library / system.object.gettype.aspx . Và bên cạnh đó, việc thực hiện một phương thức không nên thất bại hoặc thành công tùy thuộc vào cách nó được gọi. Nếu kiểu thời gian chạy của một đối tượng là một lớp con của một số lớp cơ sở thì Equals () của baseclass sẽ trả về true nếu objthực sự bằng với thisbất kể Equals () của baseclass được gọi như thế nào.
Sao Mộc

2
Di chuyển fooItemlên trên cùng và sau đó kiểm tra nó cho null sẽ hoạt động tốt hơn trong trường hợp null hoặc loại sai.
IllidanS4 muốn Monica trở lại vào

1
@ 40Alpha Vâng, vâng, sau đó obj as Foosẽ không hợp lệ.
IllidanS4 muốn Monica trở lại vào

35

Làm thế nào về:

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

Giả sử hiệu suất không phải là một vấn đề :)


1
erm - nhưng bạn đang trả về một chuỗi cho một phương thức dựa trên int; _0
jim BOTan

32
Không, anh ta gọi GetHashCode () từ đối tượng String, trả về một int.
Richard Clayton

3
Tôi không mong đợi điều này sẽ nhanh như tôi muốn, không chỉ đối với quyền anh liên quan đến các loại giá trị, mà còn về hiệu suất của string.Format. Một cái khác tôi đã thấy là new { prop1, prop2, prop3 }.GetHashCode(). Không thể bình luận mặc dù cái nào sẽ chậm hơn giữa hai cái này. Đừng lạm dụng các công cụ.
nawfal

16
Điều này sẽ trả về đúng cho { prop1="_X", prop2="Y", prop3="Z" }{ prop1="", prop2="X_Y", prop3="Z_" }. Bạn có thể không muốn điều đó.
voetsjoeba

2
Đúng, bạn luôn có thể thay thế biểu tượng gạch dưới bằng một cái gì đó không phổ biến (ví dụ •, ▲, ►,, ☺,) và hy vọng người dùng của bạn sẽ không sử dụng các biểu tượng này ... :)
Ludmil Tinkov

13

Chúng tôi có hai vấn đề để đối phó.

  1. Bạn không thể cung cấp hợp lý GetHashCode()nếu bất kỳ trường nào trong đối tượng có thể được thay đổi. Ngoài ra thường thì một đối tượng sẽ KHÔNG BAO GIỜ được sử dụng trong một bộ sưu tập phụ thuộc vào GetHashCode(). Vì vậy, chi phí thực hiện GetHashCode()thường không đáng, hoặc không thể.

  2. Nếu ai đó đặt đối tượng của bạn vào một bộ sưu tập gọi GetHashCode()và bạn đã ghi đè Equals()mà không thực hiện GetHashCode()hành vi đúng cách, người đó có thể mất nhiều ngày để theo dõi vấn đề.

Do đó theo mặc định tôi làm.

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        if (fooItem == null)
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}

5
Ném một ngoại lệ từ GetHashCode là vi phạm hợp đồng Đối tượng. Không có khó khăn để xác định một GetHashCodehàm sao cho bất kỳ hai đối tượng nào bằng nhau đều trả về cùng một mã băm; return 24601;return 8675309;cả hai sẽ được thực hiện hợp lệ của GetHashCode. Hiệu suất của Dictionarysẽ chỉ ở mức khá khi số lượng vật phẩm nhỏ và sẽ rất tệ nếu số lượng vật phẩm lớn, nhưng nó sẽ hoạt động chính xác trong mọi trường hợp.
supercat

2
@supercat, Không thể triển khai GetHashCode theo cách hợp lý nếu các trường xác định trong đối tượng có thể thay đổi, vì mã băm không bao giờ phải thay đổi. Làm những gì bạn nói có thể khiến ai đó phải mất nhiều ngày để theo dõi vấn đề hiệu suất, sau đó nhiều tuần để thiết kế lại hệ thống lớn để loại bỏ việc sử dụng từ điển.
Ian Ringrose

2
Tôi đã từng làm một cái gì đó như thế này cho tất cả các lớp mà tôi đã xác định là cần bằng () và ở đó tôi hoàn toàn chắc chắn rằng tôi không bao giờ sử dụng đối tượng đó làm khóa trong bộ sưu tập. Rồi một ngày, một chương trình mà tôi đã sử dụng một đối tượng như thế làm đầu vào cho điều khiển DevExpress XtraGrid bị hỏng. Hóa ra XtraGrid, đằng sau lưng tôi, đang tạo HashTable hoặc thứ gì đó dựa trên các đối tượng của tôi. Tôi đã có một cuộc tranh luận nhỏ với những người hỗ trợ DevExpress về việc này. Tôi đã nói rằng thật không thông minh khi họ dựa trên chức năng và độ tin cậy của thành phần của họ đối với việc triển khai một phương thức không rõ ràng của khách hàng.
RenniePet

Người DevExpress khá lén lút, về cơ bản nói rằng tôi phải là một thằng ngốc để ném một ngoại lệ trong phương thức GetHashCode (). Tôi vẫn nghĩ rằng họ nên tìm một phương pháp khác để thực hiện những gì họ đang làm - tôi nhớ lại Marc Gravell trên một chủ đề khác mô tả cách anh ta xây dựng một từ điển các đối tượng tùy ý mà không bị phụ thuộc vào GetHashCode () - không thể nhớ lại anh ta đã làm như thế nào Tuy nhiên.
RenniePet

4
@RenniePet, tốt hơn là phải lòng vì ném ngoại lệ, sau đó gặp lỗi rất khó tìm do triển khai không hợp lệ.
Ian Ringrose

12

Đó là bởi vì khung công tác yêu cầu hai đối tượng giống nhau phải có cùng mã băm. Nếu bạn ghi đè phương thức bằng để thực hiện so sánh đặc biệt của hai đối tượng và hai đối tượng được coi là giống nhau bởi phương thức, thì mã băm của hai đối tượng cũng phải giống nhau. (Từ điển và Hashtables dựa trên nguyên tắc này).


11

Chỉ cần thêm vào câu trả lời trên:

Nếu bạn không ghi đè Bằng thì hành vi mặc định là tham chiếu của các đối tượng được so sánh. Điều tương tự cũng áp dụng cho mã băm - việc cấy ghép mặc định thường dựa trên địa chỉ bộ nhớ của tham chiếu. Bởi vì bạn đã ghi đè Bằng, điều đó có nghĩa là hành vi đúng là so sánh bất cứ điều gì bạn đã thực hiện trên Bằng và không phải là các tham chiếu, vì vậy bạn nên làm tương tự cho mã băm.

Các khách hàng của lớp của bạn sẽ mong muốn mã băm có logic tương tự như phương thức bằng, ví dụ như các phương thức linq sử dụng IEqualityComparer trước tiên so sánh các mã băm và chỉ khi chúng bằng nhau, chúng sẽ so sánh phương thức Equals () có thể đắt hơn để chạy, nếu chúng tôi không triển khai mã băm, đối tượng bằng nhau có thể sẽ có các mã băm khác nhau (vì chúng có địa chỉ bộ nhớ khác nhau) và sẽ được xác định sai là không bằng nhau (Bằng (thậm chí sẽ không được nhấn).

Ngoài ra, ngoại trừ vấn đề bạn có thể không tìm thấy đối tượng của mình nếu bạn đã sử dụng nó trong từ điển (vì nó được chèn bởi một mã băm và khi bạn tìm nó, mã băm mặc định có thể sẽ khác và một lần nữa là Equals () Thậm chí sẽ không được gọi, như Marc Gravell giải thích trong câu trả lời của anh ấy, bạn cũng đưa ra vi phạm khái niệm từ điển hoặc hàm băm không cho phép các khóa giống hệt nhau - bạn đã tuyên bố rằng các đối tượng đó về cơ bản là giống nhau khi bạn vượt qua Equals để bạn không Không muốn cả hai là các khóa khác nhau trên cấu trúc dữ liệu, giả sử có một khóa duy nhất. Nhưng vì chúng có mã băm khác nhau, khóa "giống nhau" sẽ được chèn dưới dạng khác nhau.


8

Mã băm được sử dụng cho các bộ sưu tập dựa trên hàm băm như Dictionary, Hashtable, Hashset, v.v ... Mục đích của mã này là rất nhanh sắp xếp trước đối tượng cụ thể bằng cách đặt nó vào nhóm cụ thể (nhóm). Việc sắp xếp trước này giúp ích rất nhiều trong việc tìm kiếm đối tượng này khi bạn cần lấy lại từ bộ sưu tập băm vì mã phải tìm kiếm đối tượng của bạn chỉ trong một nhóm thay vì trong tất cả các đối tượng mà nó chứa. Việc phân phối mã băm tốt hơn (tính duy nhất tốt hơn) càng nhanh hơn. Trong tình huống lý tưởng nơi mỗi đối tượng có một mã băm duy nhất, việc tìm kiếm nó là một hoạt động O (1). Trong hầu hết các trường hợp, nó tiếp cận O (1).


7

Nó không nhất thiết phải quan trọng; nó phụ thuộc vào kích thước của các bộ sưu tập của bạn và các yêu cầu về hiệu suất của bạn và liệu lớp của bạn sẽ được sử dụng trong thư viện nơi bạn có thể không biết các yêu cầu về hiệu suất. Tôi thường biết kích thước bộ sưu tập của mình không lớn lắm và thời gian của tôi có giá trị hơn một vài phần triệu hiệu suất đạt được bằng cách tạo mã băm hoàn hảo; vì vậy (để thoát khỏi cảnh báo khó chịu của trình biên dịch) Tôi chỉ cần sử dụng:

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

(Tất nhiên tôi cũng có thể sử dụng #pragma để tắt cảnh báo nhưng tôi thích cách này hơn.)

Khi bạn đang ở trong vị trí mà bạn làm cần hiệu suất so với tất cả các vấn đề được đề cập bởi những người khác ở đây áp dụng, tất nhiên. Quan trọng nhất - nếu không, bạn sẽ nhận được kết quả sai khi truy xuất các mục từ bộ băm hoặc từ điển: mã băm không được thay đổi theo thời gian sống của một đối tượng (chính xác hơn, trong thời gian bất cứ khi nào cần mã băm, chẳng hạn như trong khi một khóa trong từ điển): ví dụ, sau đây là sai vì Giá trị là công khai và do đó có thể được thay đổi bên ngoài thành lớp trong suốt thời gian tồn tại của cá thể, vì vậy bạn không được sử dụng nó làm cơ sở cho mã băm:


   class A
   {
      public int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
      }
   }    

Mặt khác, nếu Giá trị không thể thay đổi, bạn có thể sử dụng:


   class A
   {
      public readonly int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //OK  Value is read-only and can't be changed during the instance's life time
      }
   }

3
Bị hạ bệ. Điều này hoàn toàn sai. Ngay cả Microsoft cũng tuyên bố trong MSDN ( msdn.microsoft.com/en-us/l Library / system.object.gethashcode.aspx ) rằng giá trị của GetHashCode PHẢI thay đổi khi trạng thái đối tượng thay đổi theo cách có thể ảnh hưởng đến giá trị trả về của cuộc gọi thành Equals () và ngay cả trong các ví dụ của nó, nó cũng cho thấy các triển khai GetHashCode hoàn toàn phụ thuộc vào các giá trị thay đổi công khai.
Sebastian PR Gingter

Sebastian, tôi không đồng ý: Nếu bạn thêm một đối tượng vào bộ sưu tập sử dụng mã băm, nó sẽ được đặt vào thùng phụ thuộc vào mã băm. Nếu bây giờ bạn thay đổi mã băm, bạn sẽ không tìm thấy đối tượng một lần nữa trong bộ sưu tập vì thùng sai sẽ được tìm kiếm. Trên thực tế, đây là điều đã xảy ra trong mã của chúng tôi và đó là lý do tại sao tôi thấy cần phải chỉ ra điều đó.
ILoveFortran

2
Ngoài ra, Sebastian, tôi không thể thấy một tuyên bố trong liên kết ( msdn.microsoft.com/en-us/l Library / system.object.gethashcode.aspx ) mà GetHashCode () phải thay đổi. Ngược lại - nó KHÔNG phải thay đổi miễn là Equals trả về cùng một giá trị cho cùng một đối số: "Phương thức GetHashCode cho một đối tượng phải luôn trả về cùng một mã băm miễn là không có sửa đổi nào cho trạng thái đối tượng xác định giá trị trả về của phương thức Equals của đối tượng. "Câu lệnh này không ngụ ý ngược lại, rằng nó phải thay đổi nếu giá trị trả về của Equals thay đổi.
ILoveFortran

2
@Joao, bạn đang nhầm lẫn giữa khách hàng / người tiêu dùng của hợp đồng với nhà sản xuất / người thực hiện. Tôi đang nói về trách nhiệm của người thực hiện, người đã ghi đè GetHashCode (). Bạn đang nói về người tiêu dùng, người đang sử dụng giá trị.
ILoveFortran

1
Hiểu nhầm hoàn toàn ... :) Sự thật là mã băm phải thay đổi khi trạng thái của đối tượng thay đổi trừ khi trạng thái không liên quan đến danh tính của đối tượng. Ngoài ra, bạn không bao giờ nên sử dụng một đối tượng MUTABLE làm khóa trong các bộ sưu tập của mình. Sử dụng các đối tượng chỉ đọc cho mục đích này. GetHashCode, bằng ... và một số phương thức khác mà tên tôi không nhớ vào lúc này KHÔNG BAO GIỜ nên ném.
darlove

0

Bạn phải luôn đảm bảo rằng nếu hai đối tượng bằng nhau, như được định nghĩa bởi Equals (), chúng sẽ trả về cùng mã băm. Như một số trạng thái bình luận khác, về lý thuyết, điều này không bắt buộc nếu đối tượng sẽ không bao giờ được sử dụng trong một thùng chứa dựa trên hàm băm như Hashset hoặc Dictionary. Tôi sẽ khuyên bạn luôn luôn tuân theo quy tắc này. Lý do đơn giản là vì quá dễ dàng để ai đó thay đổi bộ sưu tập từ loại này sang loại khác với mục đích tốt là thực sự cải thiện hiệu suất hoặc chỉ truyền đạt ngữ nghĩa mã theo cách tốt hơn.

Ví dụ: giả sử chúng tôi giữ một số đối tượng trong Danh sách. Sau đó, một người nào đó thực sự nhận ra rằng Hashset là một lựa chọn tốt hơn nhiều vì các đặc điểm tìm kiếm tốt hơn chẳng hạn. Đây là khi chúng ta có thể gặp rắc rối. Danh sách sẽ sử dụng bên trong bộ so sánh đẳng thức mặc định cho loại có nghĩa là Bằng trong trường hợp của bạn trong khi Hashset sử dụng GetHashCode (). Nếu hai người cư xử khác nhau, chương trình của bạn cũng vậy. Và hãy nhớ rằng các vấn đề như vậy không phải là dễ dàng nhất để khắc phục sự cố.

Tôi đã tóm tắt hành vi này với một số cạm bẫy GetHashCode () khác trong một bài đăng trên blog nơi bạn có thể tìm thấy các ví dụ và giải thích thêm.


0

Theo .NET 4.7phương pháp ghi đè ưa thích GetHashCode()được hiển thị dưới đây. Nếu nhắm mục tiêu các phiên bản .NET cũ hơn, hãy bao gồm gói nuget System.ValueTuple .

// C# 7.0+
public override int GetHashCode() => (FooId, FooName).GetHashCode();

Về mặt hiệu suất, phương pháp này sẽ vượt trội so với hầu hết các triển khai mã băm tổng hợp . Các ValueTuple là một structvì vậy sẽ không có bất kỳ rác, và các thuật toán cơ bản là nhanh như nó được.


-1

Theo hiểu biết của tôi, GetHashCode () ban đầu trả về địa chỉ bộ nhớ của đối tượng, do đó, điều cần thiết là ghi đè lên nó nếu bạn muốn so sánh hai đối tượng khác nhau.

EDITED: Điều đó không chính xác, phương thức GetHashCode () ban đầu không thể đảm bảo sự bằng nhau của 2 giá trị. Mặc dù các đối tượng bằng nhau trả về cùng mã băm.


-6

Dưới đây sử dụng sự phản chiếu dường như là một lựa chọn tốt hơn khi xem xét các thuộc tính công cộng vì với điều này, bạn không phải lo lắng về việc thêm / xóa các thuộc tính (mặc dù kịch bản không phổ biến lắm). Điều này tôi thấy là cũng hoạt động tốt hơn (So với thời gian sử dụng đồng hồ bấm giờ Diagonistic).

    public int getHashCode()
    {
        PropertyInfo[] theProperties = this.GetType().GetProperties();
        int hash = 31;
        foreach (PropertyInfo info in theProperties)
        {
            if (info != null)
            {
                var value = info.GetValue(this,null);
                if(value != null)
                unchecked
                {
                    hash = 29 * hash ^ value.GetHashCode();
                }
            }
        }
        return hash;  
    }

12
Việc triển khai GetHashCode () dự kiến ​​sẽ rất nhẹ. Tôi không chắc chắn việc sử dụng phản chiếu có thể nhận thấy được với StopWatch trên hàng ngàn cuộc gọi, nhưng chắc chắn là có hàng triệu người (nghĩ về việc đưa một từ điển ra khỏi danh sách).
bohdan_trotsenko
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.