鉴于System.Collections.Generic.HashSet<>
之类的集合接受null
作为集合成员,可以询问null
的哈希码应该是什么。看起来该框架使用0
:
// nullable struct type
int? i = null;
i.GetHashCode(); // gives 0
EqualityComparer<int?>.Default.GetHashCode(i); // gives 0
// class type
CultureInfo c = null;
EqualityComparer<CultureInfo>.Default.GetHashCode(c); // gives 0
对于可以为空的枚举,这可能会有些问题。如果我们定义
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
然后Nullable<Season>
(也称为Season?
)只能获取五个值,但其中两个,即null
和Season.Spring
具有相同的哈希码。< / p>
写这样一个“更好”的平等比较器是很诱人的:
class NewNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? Default.GetHashCode(x) : -1;
}
}
但是,null
的哈希码应该是0
是否有任何理由?
修改/ ADDITION:
有些人似乎认为这是重写Object.GetHashCode()
。实际上,它确实不是。 (.NET的作者确实在GetHashCode()
结构中覆盖了Nullable<>
相关的结构。)用户编写的无参数{{1}实现我们永远无法处理我们寻找的哈希码为GetHashCode()
的对象的情况。
这是关于实现抽象方法EqualityComparer<T>.GetHashCode(T)
或以其他方式实现接口方法IEqualityComparer<T>.GetHashCode(T)
。现在,在创建这些指向MSDN的链接时,我看到它说如果这些方法的唯一参数是null
,则会抛出ArgumentNullException
。这肯定是MSDN上的一个错误? .NET自己的实现都没有抛出异常。在这种情况下投掷将有效地破坏任何将null
添加到null
的尝试。除非HashSet<>
在处理HashSet<>
项目时做了一些特别的事情(我将不得不对此进行测试)。
新编辑/添加:
现在我尝试了调试。使用null
,我可以确认使用默认的相等比较器,值HashSet<>
和Season.Spring
将在同一个存储桶中结束。这可以通过非常仔细地检查私有阵列成员null
和m_buckets
来确定。请注意,索引始终按设计偏移一个。
我上面给出的代码并没有解决这个问题。事实证明,当值为m_slots
时,HashSet<>
甚至不会询问相等比较器。这来自null
的源代码:
HashSet<>
这意味着,至少 // Workaround Comparers that throw ArgumentNullException for GetHashCode(null).
private int InternalGetHashCode(T item) {
if (item == null) {
return 0;
}
return m_comparer.GetHashCode(item) & Lower31BitMask;
}
,甚至无法更改HashSet<>
的哈希值。相反,解决方法是更改所有哈希值其他值,如下:
null
答案 0 :(得分:23)
只要为类型返回的空值的哈希码是一致的,你应该没问题。对哈希码的唯一要求是两个被认为相等的对象共享相同的哈希码。
返回0或-1表示null,只要您选择一个并且一直返回它,就可以了。显然,非空哈希码不应返回用于null的任何值。
类似问题:
What should GetHashCode return when object's identifier is null?
此MSDN entry的“备注”详细介绍了哈希码。令人遗憾的是,该文档并未提供任何所有的空值的讨论或讨论 - 甚至在社区内容中也没有。
要解决enum的问题,要么重新实现哈希码以返回非零值,请添加一个等效于null的默认“unknown”枚举条目,或者只是不使用可为空的枚举。
顺便说一下,有趣的发现。我看到的另一个问题通常是哈希码不能表示可以为空的4字节或更大的类型,而 至少有一次冲突 (更多类型大小增加)。例如,int的哈希码只是int,因此它使用完整的int范围。您为该范围选择什么值为null?无论你选择哪一个都会与值的哈希码本身发生碰撞。
碰撞本身并不一定是个问题,但你需要知道它们在那里。哈希码仅在某些情况下使用。正如MSDN上的文档所述,哈希代码不能保证为不同的对象返回不同的值,因此不应该这样。
答案 1 :(得分:6)
请记住,哈希码仅用作确定相等性的第一步,[is / should]永远不会被用作关于两个对象是否相等的事实上的确定。
如果两个对象的哈希码不相等,那么它们被视为不相等(因为我们假设无效的实现是正确的 - 即我们不会猜测那个)。如果它们具有相同的哈希码,则应检查它们是否为实际相等,在您的情况下,null
和枚举值将失败。
结果 - 使用零与一般情况下的任何其他值一样好。
当然,有一些情况,比如你的枚举,这个零与真实的值的哈希码共享。问题是,对于你来说,额外比较的微不足道的开销是否会导致问题。
如果是这样,那么为你的特定类型的nullable的情况定义你自己的比较器,并确保null值总是产生一个总是相同的哈希码(当然!)和一个不能由底层类型自己的哈希码算法产生的值。对于您自己的类型,这是可行的。对于其他人 - 祝你好运:)
答案 2 :(得分:5)
为零 - 如果您愿意,可以将其设为42。
在执行程序期间,重要的是一致性。
这只是最明显的表示,因为null
通常在内部表示为零。这意味着,在调试时,如果您看到哈希码为零,则可能会提示您思考,“嗯..这是一个空引用问题吗?”
请注意,如果你使用像0xDEADBEEF
之类的数字,那么有人可能会说你正在使用一个神奇的数字......而你会有所帮助。 (你可以说零也是一个神奇的数字,你会说得对......除了它被广泛使用以至于对规则有点例外。)
答案 3 :(得分:4)
好问题。
我只是想对此进行编码:
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
并执行此操作:
Season? v = null;
Console.WriteLine(v);
返回null
如果我这样做,而不是正常的
Season? v = Season.Spring;
Console.WriteLine((int)v);
如果我们避免转换为0
,它会按预期返回int
或简单 Spring 。
所以......如果你这样做:
Season? v = Season.Spring;
Season? vnull = null;
if(vnull == v) // never TRUE
编辑
来自MSDN
如果两个对象比较相等,则每个对象的GetHashCode方法必须返回相同的值。 但是,如果两个对象的比较不相等,则两个对象的GetHashCode方法不必返回不同的值
换句话说:如果两个对象具有相同的哈希码并不意味着它们相等,则实际相等由 Equals 决定。
再次从MSDN:
对象的GetHashCode方法必须始终返回相同的值 哈希码只要没有对象状态的修改即可 确定对象的Equals方法的返回值。注意 这仅适用于当前执行的应用程序,并且 如果运行应用程序,则可以返回不同的哈希代码 试。
答案 4 :(得分:4)
但是有没有理由为什么null的哈希码应该是0?
它本可以是任何东西。我倾向于同意0不一定是最好的选择,但它可能导致最少的错误。
绝对的哈希函数必须为相同的值返回相同的哈希值。一旦存在执行此操作的 组件,这实际上是null
哈希值的唯一有效值。如果有一个常量,例如,hm,object.HashOfNull
,那么实现IEqualityComparer
的人必须知道使用该值。如果他们没有考虑,那么他们使用0的机会略高于其他每一个值,我估计。
至少对于HashSet&lt;&gt;,甚至无法更改null的散列
如上所述,我认为完全停止是完全不可能的,因为存在类型已经遵循惯例,即null的哈希为0。
答案 5 :(得分:2)
为简单起见,它为0。没有这样的硬性要求。您只需要确保哈希编码的一般要求。
例如,您需要确保如果两个对象相等,则它们的哈希码必须始终相等。因此,不同的哈希码必须始终代表不同的对象(但不一定是真的反过来:两个不同的对象可能具有相同的哈希码,即使这经常发生,那么这不是一个好的哈希函数 - 它没有良好的抗碰撞性。)
当然,我限制了对数学本质要求的回答。还有特定于.NET的技术条件,您可以阅读here。 0表示空值不在其中。
答案 6 :(得分:1)
因此可以通过使用Unknown
枚举值来避免这种情况(尽管Season
未知似乎有点奇怪)。所以这样的事情会否定这个问题:
public enum Season
{
Unknown = 0,
Spring,
Summer,
Autumn,
Winter
}
Season some_season = Season.Unknown;
int code = some_season.GetHashCode(); // 0
some_season = Season.Autumn;
code = some_season.GetHashCode(); // 3
然后,每个季节都会有唯一的哈希码值。
答案 7 :(得分:1)
就我个人而言,我发现使用可空值有点尴尬,并尽量避免使用它们。你的问题只是另一个原因。有时它们非常方便,但我的经验法则不是将值类型与null混合,如果可能的话,因为它们来自两个不同的世界。在.NET框架中,它们似乎也是如此 - 许多值类型提供了TryParse
方法,这种方法可以将值与无值(null
)分开。
在您的特定情况下,您可以轻松解决问题,因为您处理自己的Season
类型。
(Season?)null
对我来说意味着“没有指定季节”,就像你有一个不需要某些字段的webform一样。在我看来,最好在enum
本身中指定特殊的“值”,而不是使用一点笨重的Nullable<T>
。它会更快(没有拳击)更容易阅读(Season.NotSpecified
vs null
),并将解决您的哈希码问题。
当然对于其他类型,例如int
,您无法扩展值域并将其中一个值命名为 special 并非总是可行。但是使用int?
哈希代码碰撞是一个小得多的问题,如果有的话。
答案 8 :(得分:0)
Tuple.Create( (object) null! ).GetHashCode() // 0
Tuple.Create( 0 ).GetHashCode() // 0
Tuple.Create( 1 ).GetHashCode() // 1
Tuple.Create( 2 ).GetHashCode() // 2