1

我有一个不可变的值对象 IPathwayModule,其值由以下内容定义:

  • (int) 块;
  • (实体)模块,由(字符串)ModuleId 标识;
  • (枚举)状态;和
  • (实体)类,由(字符串)ClassId 标识 - 可以为 null。

这是我当前的 IEqualityComparer 实现,它似乎适用于一些单元测试。但是,我认为我不了解自己做得好到足以知道自己做得是否正确。以前的实现有时会在重复测试运行时失败。

private class StandardPathwayModuleComparer : IEqualityComparer<IPathwayModule>
{
    public bool Equals(IPathwayModule x, IPathwayModule y)
    {
        int hx = GetHashCode(x);
        int hy = GetHashCode(y);
        return hx == hy;
    }

    public int GetHashCode(IPathwayModule obj)
    {
        int h;
        if (obj.Class != null)
        {
            h = obj.Block.GetHashCode() + obj.Module.ModuleId.GetHashCode() + obj.Status.GetHashCode() + obj.Class.ClassId.GetHashCode();
        }
        else
        {
            h = obj.Block.GetHashCode() + obj.Module.ModuleId.GetHashCode() + obj.Status.GetHashCode() + "NOCLASS".GetHashCode();
        }
        return h;
    }
}

IPathwayModule 绝对是不可变的,具有相同值的不同实例应该相等并产生相同的 HashCode,因为它们被用作 HashSet 中的项目。

我想我的问题是:

  • 在这种情况下我是否正确使用了界面?
  • 是否存在我可能看不到所需行为的情况?
  • 有什么方法可以提高鲁棒性、性能吗?
  • 有没有我没有遵循的良好做法?
4

7 回答 7

4

不要根据 Hash 函数的结果执行 Equals,因为它太脆弱了。而是对每个字段进行字段值比较。就像是:

return x != null && y != null && x.Name.Equals(y.Name) && x.Type.Equals(y.Type) ...

此外,散列函数的结果并不适合添加。尝试改用^运算符。

return obj.Name.GetHashCode() ^ obj.Type.GetHashCode() ...

您不需要 GetHashCode 中的空值检查。如果该值为空,那么您将遇到更大的问题,尝试从您无法控制的事物中恢复是没有用的...

于 2009-10-14T11:24:51.597 回答
3

唯一的大问题是 Equals 的实现。哈希码不是唯一的,您可以为不同的对象获得相同的哈希码。您应该单独比较 IPathwayModule 的每个字段。

GetHashCode() 可以改进一点。您不需要在 int 上调用 GetHashCode()。int 本身就是一个很好的哈希码。枚举值也是如此。然后可以像这样实现您的 GetHashCode:

public int GetHashCode(IPathwayModule obj)
{
    unchecked {
        int h = obj.Block + obj.Module.ModeleId.GetHashCode() + (int) obj.Status;
        if (obj.class != null)
           h += obj.Class.ClassId.GetHashCode();
        return h;
    }
}

'unchecked' 块是必要的,因为算术运算中可能存在溢出。

于 2009-10-14T11:29:46.343 回答
2

您不应该使用 GetHashCode() 作为比较对象的主要方式。比较它的领域。

可能有多个对象具有相同的哈希码(这称为“哈希码冲突”)。

此外,将多个整数值相加时要小心,因为您很容易导致溢出异常。使用 'exclusive or' (^) 组合哈希码或将代码包装到 'unchecked' 块中。

于 2009-10-14T11:24:30.927 回答
1

您应该实现更好的 Equals 和 GetHashCode 版本。

例如,枚举的哈希码就是它们的数值。

换句话说,使用这两个枚举:

public enum A { x, y, z }
public enum B { k, l, m }

然后使用您的实现,以下值类型:

public struct AB {
    public A;
    public B;
}

以下两个值将被视为相等:

AB ab1 = new AB { A = A.x, B = B.m };
AB ab2 = new AB { A = A.z, B = B.k };

我假设你不想要那个。

此外,将值类型作为接口传递会将它们装箱,这可能会带来性能问题,尽管可能并不多。您可以考虑让 IEqualityComparer 实现直接采用您的值类型。

于 2009-10-14T11:25:19.117 回答
1

如果我很好理解你,你想听听对你的代码的一些评论。以下是我的评论:

  1. GetHashCode应该一起异或,而不是相加。XOR ( ^) 提供了更好的防止碰撞的机会
  2. 你比较哈希码。这很好,但只有在底层对象覆盖GetHashCode. 如果没有,请使用属性及其哈希码并将它们组合起来。
  3. 哈希码很重要,它们可以进行快速比较。但是如果哈希码相等,对象仍然可以不同。这种情况很少发生。但是,如果哈希码相等,您需要比较对象的字段。
  4. 你说你的值类型是不可变的,但你引用的对象(.Class)不是不可变的
  5. 始终通过添加参考比较作为第一个测试来优化比较。引用不等,对象不等,结构不等。

第 5 点取决于您是否希望在值类型中引用的对象在不同引用时返回不相等。

编辑:你比较了很多字符串。字符串比较在 C# 中进行了优化。正如其他人建议的那样,您可以在比较中更好地使用==它们。对于 GetHashCode,^也按照其他人的建议使用 OR。

于 2009-10-14T11:32:10.143 回答
1
  1. 假设两个对象是相等的,因为它们的哈希码相等是错误的。您需要单独比较所有成员
  2. 使用 ^ 而不是 + 组合哈希码可能更好。
于 2009-10-14T11:52:43.380 回答
0

感谢所有回复的人。我汇总了每个回复的人的反馈,我IEqualityComparer现在的改进如下:

private class StandardPathwayModuleComparer : IEqualityComparer<IPathwayModule>
{
    public bool Equals(IPathwayModule x, IPathwayModule y)
    {
        if (x == y) return true;
        if (x == null || y == null) return false;

        if ((x.Class == null) ^ (y.Class == null)) return false;

        if (x.Class == null) //and implicitly y.Class == null
        {
            return x.Block.Equals(y.Block) && x.Status.Equals(y.Status) && x.Module.ModuleId.Equals(y.Module.ModuleId);
        }
        return x.Block.Equals(y.Block) && x.Status.Equals(y.Status) && x.Module.ModuleId.Equals(y.Module.ModuleId) && x.Class.ClassId.Equals(y.Class.ClassId);
    }
    public int GetHashCode(IPathwayModule obj)
    {
        unchecked {
            int h = obj.Block ^ obj.Module.ModuleId.GetHashCode() ^ (int) obj.Status;
            if (obj.Class != null)
            {
               h ^= obj.Class.ClassId.GetHashCode();
            }
            return h;
        }
    }
}
于 2009-10-14T12:29:55.947 回答