【问题标题】:Hashing an array in c#在c#中散列一个数组
【发布时间】:2021-11-21 15:12:49
【问题描述】:

小问题

如何为Array 实现GetHashCode

详情

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

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

对于array 中的所有n

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

hash = hash ^ array[n]

澄清

我的对象包含一个数组,我对数组元素的 GetHashCode 感兴趣。我的数组等价代码仅作为示例 - 就像我的问题所说但也许我不清楚,我对GetHashCode(不是Equals)感兴趣。我说我自然应该实现互补的GetHashCode,因为一旦Equals 被覆盖(Dictionary 等才能正常运行),.NET 就需要实现它。谢谢。

【问题讨论】:

  • 查看here 发布的答案。换句话说,您最好实现自己的变体或使用其他工具,您不能将GetHashCode()Equals() 用于数组
  • 为什么不把this.array[n].Equals(otherObject.array[n]) 换成n
  • 如果要比较两个数组是否相等,可以使用SequenceEqual扩展
  • @c z:请说明array 是否是您要覆盖其 Equals 和 GetHashCode 的对象中的一个字段。

标签: c# .net arrays hash


【解决方案1】:

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

(强制转换是必要的,因为 Array 类显式地实现了该方法。)

例如,如果你的对象有一个int 数组,那么你可以像这样实现GetHashCode:

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

如果您好奇,下面是 Array 类如何实现 GetHashCode 方法(来自Reference Source):

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

如您所见,当前的实现只使用了数组的最后八个元素。

【讨论】:

    【解决方案2】:

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

    迈克尔上面回答的一个选项是基于数组元素的哈希码。这将符合您的 Equals 值语义。但是,因为“作为准则,对象的哈希在对象的整个生命周期内必须相同”,因此您必须确保数组在获取其哈希码后不会更改。拥有一个要求它永不改变的非不可变容器对我来说听起来很容易出错。

    您的另一个(IMO 更好的选择)是切换到不可变容器(即 ImmutableArray),然后基于值的哈希码才有意义。您可以像上面一样使用IStructuralEquatable,也可以更一般地使用:

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

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

    【讨论】:

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

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

    public bool Equals
    

    0n 个对象数组

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

    【讨论】:

    • OP 已经在一个 包含 数组的对象上实现了 Equals。在该对象上实现 GetHashCode 也是很自然的。
    • @MichaelLiu 不是我的阅读方式。我没有读取 包含 数组的对象。我将其读取为对象 in 数组覆盖等于 this.array[n] == otherObject.array[n].
    • 为什么数组in中的对象会有一个引用this.array的Equals方法?这意味着你有一个对象数组,而这些对象又包含数组。
    • 不,不是那个意思。你正在阅读一些不存在的东西。我同意为什么数组中的项目需要引用数组?有一个巧妙的内置方法用于比较两个数组,该方法使用数组 in 项的默认相等比较器。
    • “我不同意你应该自然地在数组上实现 GetHashCode” - 如果你在覆盖 equals 时不实现 GetHashCode,Dictionary 的行为会非常奇怪,所以我真的需要 GetHashCode .
    【解决方案4】:

    使用当前框架可以考虑使用

    int value=0;
    for (var i = 0;i< this.array.Length; i++)
    {
        value=HashCode.Combine(this.array[i],value);
    }
    

    【讨论】:

      猜你喜欢
      • 2011-05-08
      • 2015-04-16
      • 1970-01-01
      • 2012-11-28
      • 1970-01-01
      • 2012-06-01
      • 1970-01-01
      • 2012-08-16
      相关资源
      最近更新 更多