在C#中对数组进行哈希处理

25

简短问题

如何为Array实现GetHashCode

细节

我有一个对象,覆盖了Equals,检查:

this.array[n] == otherObject.array[n]

对于数组中的所有n

自然地,我应该实现相应的GetHashCode。我想知道是否有 .NET 的方法可以做到这一点,或者我应该自己实现,例如:

hash = hash ^ array[n]

澄清

我的对象包含一个数组,我对数组元素的GetHashCode感兴趣。我的数组等效性代码只是举例说明-就像我的问题所说,但也许我没有表达清楚,我对GetHashCode(而不是Equals)感兴趣。我说我应该自然地实现补充GetHashCode,因为在重写Equals(为了使Dictionary等正确运行)后,.NET要求实现此功能。谢谢。


请查看此处发布的答案:https://dev59.com/K2w05IYBdhLWcg3wcRUj#7244729。换句话说,最好实现自己的变体或使用其他工具,不能对数组使用 GetHashCode()Equals() - Draken
为什么不对于 nthis.array[n].Equals(otherObject.array[n]) - Mathias R. Jessen
1
如果您想比较两个数组是否相等,可以使用 SequenceEqual 扩展。 - Mike Debela
@c z: 请澄清一下您是否正在为其覆盖Equals和GetHashCode的对象中的 array 字段。 - Michael Liu
可能是GetHashCode override of object containing generic array的重复问题。 - Christian Gollhardt
4个回答

25

要使用数组的元素计算哈希码,您可以将数组强制转换为IStructuralEquatable,然后调用GetHashCode(IEqualityComparer) 方法,传递一个用于数组元素类型的比较器。

(需要进行强制转换,因为Array类显式实现了该方法。)

例如,如果您的对象具有一个int数组,则可以按如下方式实现GetHashCode:

public override int GetHashCode()
{
    return ((IStructuralEquatable)this.array).GetHashCode(EqualityComparer<int>.Default);
}

如果你好奇的话,这里是Array类如何实现GetHashCode方法的代码(来自参考源代码):

internal static int CombineHashCodes(int h1, int h2) {
    return (((h1 << 5) + h1) ^ h2);
}

int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
    if (comparer == null)
        throw new ArgumentNullException("comparer");
    Contract.EndContractBlock();

    int ret = 0;

    for (int i = (this.Length >= 8 ? this.Length - 8 : 0); i < this.Length; i++) {
        ret = CombineHashCodes(ret, comparer.GetHashCode(GetValue(i)));
    }

    return ret;
}

正如您所看到的,当前实现仅使用数组的最后八个元素。


4

这取决于您想要什么...

作为Michael在上面回答的一个选择是基于数组元素具有散列码。这将与您的Equals值语义一致。然而,因为“作为指导原则,对象的哈希必须在对象的整个生命周期内保持不变”,因此您必须确保在获取其哈希码后,您的数组不会改变。对于具有永远不会更改的需求的非不可变容器来说,听起来很容易出错。

您的另一个(我认为更好的选择)是切换到不可变容器(即ImmutableArray),那么基于值的哈希码就有意义了。您可以像上面一样使用IStructuralEquatable或更普遍地使用:

    public override GetHashCode() =>
        Value.Aggregate(0, (total, next) => HashCode.Combine(total, next));

这也适用于其他不可变集合。


1
使用Array.GetHashCode()肯定是错误的,因为对于两个具有相等元素的数组它将返回不同的值,而OP需要它返回一个相同的值。显然,在获取其结构哈希码之后,必须确保不会修改数组的内容,如果这个数组是对象的私有成员,那么这是可能实现的。(鉴于数组具有固定大小,我假设这是你所说的“添加/删除元素”) - Michael Liu
你是对的!我编辑了我的回答。似乎没有一种“好”的解决方案可以将具有值语义的非不可变集合存储为其他集合的元素。 - kofifus

2
使用当前框架,可以考虑使用。
int value=0;
for (var i = 0;i< this.array.Length; i++)
{
    value=HashCode.Combine(this.array[i],value);
}

1

我不同意你应该在数组上自然地实现GetHashCode
因为你必须随着每一个改变而更新它
或者即时计算
我会直接进行比较
SequenceEquals将使用默认的相等比较器,因此您还应该实现

public bool Equals

在数组中的对象上 Enumerable.SequenceEqual 有一个示例
public static void SequenceEqualEx1()
{
    Pet pet1 = new Pet { Name = "Turbo", Age = 2 };
    Pet pet2 = new Pet { Name = "Peanut", Age = 8 };

    // Create two lists of pets.
    List<Pet> pets1 = new List<Pet> { pet1, pet2 };
    List<Pet> pets2 = new List<Pet> { pet1, pet2 };

    bool equal = pets1.SequenceEqual(pets2);

    Console.WriteLine(
        "The lists {0} equal.",
        equal ? "are" : "are not");
}

3
原文:The OP has implemented Equals on an object which contains an array. It is natural to implement GetHashCode on that object as well.翻译:OP在一个包含数组的对象上实现了Equals方法。因此,同时在该对象上实现GetHashCode方法是很自然的事情。 - Michael Liu
@MichaelLiu 我的理解不是读取一个包含数组的对象。我是这样理解的:数组中的对象覆盖了equals方法,即this.array[n] == otherObject.array[n]。 - paparazzo
1
为什么数组中的对象会有一个引用this.array的Equals方法?这意味着您拥有一个包含数组的对象数组。 - Michael Liu
“我不同意你应该在数组上自然实现GetHashCode” - 如果您在重写equals时不实现GetHashCode,则Dictionary<T, U>的行为非常奇怪,因此我确实需要GetHashCode。 - c z
@cz 这是你本可以在问题中包含的信息。你真的有一个字典,其中键是实现数组的对象吗? - paparazzo
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接