哈希码是一个数字值,用于在基于哈希的集合中插入和标识对象,例如Dictionary <TKey,TValue>类,Hashtable类或从DictionaryBase类派生的类型。所述的GetHashCode方法提供了一种用于需要对象平等快速检查算法此哈希码。相等的两个对象返回相等的哈希码。但是,事实并非如此:相等的哈希码并不意味着对象相等,因为不同的(不相等)对象可以具有相同的哈希码。此外,.NET不保证GetHashCode方法的默认实现,并且此方法返回的值在.NET实现(例如,不同版本的.NET Framework和.NET Core)以及平台(例如32位和64位平台。由于这些原因,请勿将此方法的默认实现用作唯一的对象标识符以进行哈希处理。本文主要介绍GetHashCode和Equals方法重写实现及示例代码。

1、单个数据字段GetHashCode和Equals方法重写实现

计算与Int32类型具有相同或较小范围的数值的哈希码的最简单方法之一就是简单地返回该值。以下示例显示了这种Number结构的实现。

using System;
public struct Number
{
   private int n;
   public Number(int value)
   {
      n = value;
   }
   public int Value
   {
      get { return n; }
   }
   public override bool Equals(Object obj)
   {
      if (obj == null || ! (obj is Number)) 
         return false;
      else
         return n == ((Number) obj).n;
   }      
   public override int GetHashCode()
   {
      return n;
   }
   public override string ToString()
   {
      return n.ToString();
   }
}
public class Example
{
   public static void Main()
   {
      Random rnd = new Random();
      for (int ctr = 0; ctr <= 9; ctr++) {
         int randomN = rnd.Next(Int32.MinValue, Int32.MaxValue);
         Number n = new Number(randomN);
         Console.WriteLine("n = {0,12}, hash code = {1,12}", n, n.GetHashCode());
      }   
   }
}
// The example displays output like the following:
//       n =   -634398368, hash code =   -634398368
//       n =   2136747730, hash code =   2136747730
//       n =  -1973417279, hash code =  -1973417279
//       n =   1101478715, hash code =   1101478715
//       n =   2078057429, hash code =   2078057429
//       n =   -334489950, hash code =   -334489950
//       n =    -68958230, hash code =    -68958230
//       n =   -379951485, hash code =   -379951485
//       n =    -31553685, hash code =    -31553685
//       n =   2105429592, hash code =   2105429592
2、多个数据字段GetHashCode和Equals方法重写实现

一个类型具有多个数据字段,这些数据字段可以参与生成哈希码。生成哈希码的一种方法是使用XOR (eXclusive OR)操作来组合这些字段,如以下示例所示。

using System;
// A type that represents a 2-D point.
public struct Point
{
    private int x;
    private int y;
    public Point(int x, int y)
    {
       this.x = x;
       this.y = y;
    }
    public override bool Equals(Object obj)
    {
       if (! (obj is Point)) return false;
       Point p = (Point) obj;
       return x == p.x & y == p.y;
    }
    public override int GetHashCode()
    { 
        return x ^ y;
    } 
} 
public class Example
{
   public static void Main()
   {
      Point pt = new Point(5, 8);
      Console.WriteLine(pt.GetHashCode());
      pt = new Point(8, 5);
      Console.WriteLine(pt.GetHashCode());
   }
}
// The example displays the following output:
//       13
//       13

3、数据字段顺序不同GetHashCode和Equals方法重写实现

(n1,n2)和(n2,n1)返回相同的哈希码,因此可能会产生比期望更多的冲突。有许多解决方案可用,因此这些情况下的哈希码不相同。一种是返回Tuple反映每个字段顺序的对象的哈希码。以下示例显示了使用Tuple <T1,T2>类的可能实现。但是请注意,实例化Tuple对象的性能开销可能会严重影响在哈希表中存储大量对象的应用程序的整体性能。

using System;
public struct Point
{
private int x;
private int y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
public override bool Equals(Object obj)
{
if (!(obj is Point)) return false;
Point p = (Point) obj;
return x == p.x & y == p.y;
}
public override int GetHashCode()
{
return Tuple.Create(x, y).GetHashCode();
}
}
public class Example
{
public static void Main()
{
Point pt = new Point(5, 8);
Console.WriteLine(pt.GetHashCode());
pt = new Point(8, 5);
Console.WriteLine(pt.GetHashCode());
}
}
// The example displays the following output:
// 173
// 269

或者

using System;
public struct Point
{
    private int x;
    private int y;
    public Point(int x, int y)
    {
       this.x = x;
       this.y = y;
    }
    public override bool Equals(Object obj)
    {
       if (!(obj is Point)) return false;
       Point p = (Point) obj;
       return x == p.x & y == p.y;
    }
    public override int GetHashCode()
    { 
        return ShiftAndWrap(x.GetHashCode(), 2) ^ y.GetHashCode();
    } 
    private int ShiftAndWrap(int value, int positions)
    {
        positions = positions & 0x1F;
        // Save the existing bit pattern, but interpret it as an unsigned integer.
        uint number = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
        // Preserve the bits to be discarded.
        uint wrapped = number >> (32 - positions);
        // Shift and wrap the discarded bits.
        return BitConverter.ToInt32(BitConverter.GetBytes((number << positions) | wrapped), 0);
    }
} 
public class Example
{
   public static void Main()
   {
        Point pt = new Point(5, 8);
        Console.WriteLine(pt.GetHashCode());
        pt = new Point(8, 5);
        Console.WriteLine(pt.GetHashCode());
   }
}
// The example displays the following output:
//       28
//       37 

注意:

哈希码用于在基于哈希表的集合中进行有效的插入和查找。哈希码不是永久值。为此原因:

1) 不要序列化哈希码值或将其存储在数据库中。

2) 不要将哈希码用作从键控集合中检索对象的键。

3) 不要跨应用程序域或进程发送哈希码。在某些情况下,可以在每个进程或每个应用程序域的基础上计算哈希码。

4) 如果您需要加密强度高的哈希,请不要使用哈希码代替加密哈希函数返回的值。对于加密哈希,请使用派生自System.Security.Cryptography.HashAlgorithmSystem.Security.Cryptography.KeyedHashAlgorithm类的类。

5) 不要测试哈希码是否相等,以确定两个对象是否相等。(不相等的对象可以具有相同的哈希码。)要测试是否相等,请调用ReferenceEqualsEquals方法。

6) 如果重写GetHashCode方法,则还应该重写Equals,反之亦然。如果想要测试重写的Equals方法时,则重写的GetHashCode方法必须为两个对象返回相同的值。

相关文档:

System_Object_GetHashCode

.NET Core(C#) IEqualityComparer<in T>接口的使用方法及示例代码

推荐文档