导出 (0) 打印
全部展开
此文章由机器翻译。 将光标移到文章的句子上,以查看原文。 更多信息。
译文
原文

Object.GetHashCode 方法

作为默认哈希函数。

命名空间:  System
程序集:  mscorlib(在 mscorlib.dll 中)

public virtual int GetHashCode()

返回值

类型:System.Int32
当前对象的哈希代码。

哈希代码是用于插入的数值并确定在基于哈希的集合的对象 (如 Dictionary<TKey, TValue> 选件类、Hashtable 选件类或从 DictionaryBase 选件类派生类型。 GetHashCode 方法为需要对象相等快速检查的算法提供此哈希代码。

说明说明

有关哈希码如何用于哈希表以及一些其他的哈希代码算法的信息,请参见在 Wikipedia Hash Function 中的项。

相等的两个对象返回相同的哈希码。 但是,如果反转不为真:相等哈希码不表示对象相等,因为不同的 (不相等) 对象可能具有相同的哈希码。 此外,.NET Framework 不保证 GetHashCode 方法的默认实现,并且此方法返回的值于 .NET Framework 版本和平台之间可能不同,如 32 位和 64 位平台。 因此,在进行哈希运算时,该方法的默认实现不得用作唯一对象标识符。 两种结果,如下:

  • 您不应该假定,相等哈希代码表示对象相等。

  • 您不应保留或使用在创建的应用程序域以外的哈希代码,因为同一对象可以哈希跨应用程序域,进程和平台。

警告说明警告

哈希代码用来在基于哈希表的集合中进行有效的插入和查找。 哈希代码不是一个永久值。 此目的:

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

  • 不要使用哈希代码作为关键字从键控集合中检索对象。

  • 不要跨应用程序域或进程发送哈希码。 有时,可以基于每个进程或每个应用程序域基类型计算哈希代码。

  • 如果需要密码强哈希,不要使用哈希代码而是加密哈希函数返回的值。 对于加密哈希,请使用来自 System.Security.Cryptography.HashAlgorithmSystem.Security.Cryptography.KeyedHashAlgorithm 类的派生类。

  • 不要测试的哈希代码的相等性来确定两个对象是否相等。(不相等的对象可能有相同的哈希代码。)若要测试相等性,请调用 ReferenceEqualsEquals 方法。

GetHashCode 方法可以由派生类型重写。 如果 GetHashCode 没有重写,引用类型的哈希代码通过调用基类的 Object.GetHashCode 方法计算,它基于对象的引用的哈希代码计算;有关更多信息,请参见 RuntimeHelpers.GetHashCode 换言之,ReferenceEquals 方法返回 true 的两个对象具有相同的哈希代码。 如果值类型不重写 GetHashCode,基类的 ValueType.GetHashCode 方法使用反射计算基于类型字段的值的哈希代码。 换言之,字段有相同值的值类型有相等的哈希代码。 有关重写的 GetHashCode的详细信息,请参见"Notes to Inheritors"一节。

警告说明警告

如果重写 GetHashCode 方法,还应重写 Equals,反之亦然。 当两个对象用于相等测试时,如果重写的 Equals 方法返回 true,重写的 GetHashCode 方法必须返回两个对象的相同值。

如果在哈希表中的作为键使用的对象不提供一个有用的 GetHashCode的实现,可以通过提供 IEqualityComparer 实现对Hashtable 类构造函数之一的重载指定哈希代码提供程序。

Windows 运行时的说明

当您调用 Windows 运行时中的类的重载 GetHashCode 方法时,它会针对不重写 GetHashCode的类提供默认值行为。 这是由 .NET Framework 为 Windows 运行时 提供的支持的一部分 (请参见.NET Framework 对 Windows 应用商店应用程序和 Windows 运行时的支持情况)。 在 Windows 运行时 的类不继承 Object,当前不执行 GetHashCode 但是,控件显示具有 ToStringEquals(Object)GetHashCode 方法,当您在 C# 或 Visual Basic 代码时,.NET Framework,并为这些方法提供默认值行为。

说明说明

在 C# 或 Visual Basic 编写的Windows 运行时 类可以重写方法 GetHashCode

对继承者的说明

哈希函数用于快速生成一个与对象的值相对应的数字(哈希代码)。 哈希函数通常是特定于每个类型的,而且,必须只能使用一个实例字段作为输入。 不应计算使用静态字段的值的哈希代码。

对于从 Object派生的类,只有当派生类定义相等性为引用相等性,GetHashCode 方法可以委托到基类 Object.GetHashCode() 实现。 GetHashCode 对引用类型的默认值实现返回等效于 RuntimeHelpers.GetHashCode(Object) 方法返回的哈希代码。 您可以重写不可变的引用类型的 GetHashCode 通常,对于可变引用类型,则应该重写 GetHashCode,只有当:

  • 可以从不可变字段的哈希代码计算;或

  • 可以确保可变对象的哈希代码不更改,而集合中包含的对象依赖其哈希代码。

否则,您可能认为变量的对象在哈希表中丢失。 如果选择重写可变引用类型的 GetHashCode,文档应明确指出您的类型的使用者不应修改对象值,该对象在哈希表中存储。

对于值类型,ValueType.GetHashCode 提供使用反射的默认哈希代码实现。 更好的性能,应考虑重写它。

说明说明

有关以各种方式计算哈希代码的更多信息和示例,请参见的"示例"部分。

哈希函数必须具有以下特点:

  • 如果两个对象的比较结果相等,则每个对象的 GetHashCode 方法都必须返回同一个值。 但是,如果两个对象的比较结果不相等,则这两个对象的 GetHashCode 方法不一定返回不同的值。

  • 一个对象的 GetHashCode 方法必须总是返回同一个哈希代码,但前提是没有修改过对象状态,对象状态用来确定对象的 Equals 方法的返回值。 请注意,这仅适用于应用程序的当前执行,再次运行该应用程序时可能会返回另一个哈希代码。

  • 为了获得最佳性能,哈希函数应生成所有输入的一个均匀分布,包括大量群集的输入。 问题是对象状态的小修改会导致大修改,以获得最佳的哈希表的性能所产生的哈希码。

  • 哈希函数的计算成本必须不高。

  • GetHashCode 方法不应引发异常。

例如,String 类提供的 GetHashCode 方法的实现为相同的字符串值返回相同的哈希代码。 因此,如果两个 String 对象表示相同的字符串值,则它们返回相同的哈希代码。 另外,该方法使用字符串中的所有字符生成相当随机的分布式输出,即使当输入集中在某些范围内时(例如,许多用户可能有只包含低位 128 个 ASCII 字符的字符串,即使字符串可以包含 65,535 个 Unicode 字符中的任何字符)。

在类上提供好的哈希函数可以显著影响将这些对象添加到哈希表的性能。 在具有好的哈希函数实现的带键值哈希表中,搜索元素所用的时间是固定的(例如运算复杂度为 O(1) 的运算)。 而在具有不好的哈希函数实现的哈希表中,搜索性能取决于哈希表中的项数(例如运算复杂度为 O(n) 的运算,其中的 n 是哈希表中的项数)。 恶意用户输入数据增加冲突数,可以显着降低依赖于哈希表的应用程序性能,例如:

  • 当哈希函数生成常见的冲突。

  • 当哈希表的一个大比例对象生成相互等于或大致相等的哈希代码。

  • 当用户输入哈希代码计算的数据。

重写 GetHashCode 的派生类还必须重写 Equals,以保证被视为相等的两个对象具有相同的哈希代码;否则,Hashtable 类型可能无法正常工作。

其中一种简单计算具有相同的或比 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


一个类型常具有多个可以参与生成哈希代码的数据字段。 生成哈希代码的一种方法是使用 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


前面的示例返回 (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


第二种替代方案涉及通过左移两位或更多位的连续字段的哈希代码来加权各个哈希代码。 最好地,超过31位的位移应该包装,而不是被丢弃。 因为在 c# 和 Visual Basic 的左移运算符中位被丢弃,这需要创建类似以下内容的shift-and-wrap方法:


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


下面的示例使用此 shift-and-wrap 方法计算上一示例中使用的 Point 结构的哈希代码。


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 


.NET Framework

受以下版本支持:4.5.2、4.5.1、4.5、4、3.5、3.0、2.0、1.1、1.0

.NET Framework Client Profile

受以下版本支持:4、3.5 SP1

可移植类库

受以下版本支持:可移植类库

适用于 Windows 应用商店应用的 .NET

受以下版本支持:Windows 8

适用于 Windows Phone 应用的 .NET

受以下版本支持:Windows Phone 8、Silverlight 8.1

Windows Phone 8.1, Windows Phone 8, Windows 8.1, Windows Server 2012 R2, Windows 8, Windows Server 2012, Windows 7, Windows Vista SP2, Windows Server 2008(不支持服务器核心角色), Windows Server 2008 R2(支持带 SP1 或更高版本的服务器核心角色;不支持 Itanium)

.NET Framework 并不是对每个平台的所有版本都提供支持。有关支持的版本的列表,请参阅.NET Framework 系统要求

社区附加资源

添加
显示:
© 2014 Microsoft