繁体   English   中英

IEqualityComparer用于值对象

[英]IEqualityComparer for Value Objects

我有一个不变的值对象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,因为它们被用作HashSets中的项目。

我想我的问题是:

  • 在这种情况下,我可以正确使用接口吗?
  • 在某些情况下,我可能看不到预期的行为吗?
  • 有什么方法可以提高鲁棒性,性能?
  • 有没有我没有遵循的良好做法?

不要对Hash函数的结果做等于,它太脆弱了。 而是对每个字段进行字段值比较。 就像是:

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

另外,哈希函数的结果并不真正适合添加。 尝试改用^运算符。

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

您不需要在GetHashCode中进行null检查。 如果该值为null,那么您将遇到更大的问题,试图从您无法控制的事物中恢复将毫无用处...

唯一的大问题是平等的实施。 哈希码不是唯一的,对于不同的对象,您可以获得相同的哈希码。 您应该分别比较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”块是必需的,因为算术运算中可能会溢出。

您不应该使用GetHashCode()作为比较对象的主要方式。 在现场进行比较。

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

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

您应该实现更好的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实现直接采用您的值类型。

如果我对您的理解很好,那么您希望听到一些关于您代码的评论。 这是我的话:

  1. GetHashCode应该一起XOR,而不是添加。 XOR( ^ )可以更好地防止碰撞
  2. 您比较哈希码。 很好,但是只有在基础对象覆盖GetHashCode时才这样做。 如果不是,请使用属性及其哈希码并将其组合。
  3. 哈希码很重要,它们使快速比较成为可能。 但是,如果哈希码相等,则对象仍然可以不同。 这种情况很少发生。 但是,如果哈希码相等,则需要比较对象的字段。
  4. 您说值类型是不可变的,但是您引用的对象( .Class )却是不可变的
  5. 始终通过添加参考比较作为第一项测试来优化比较。 引用不相等,对象不相等,则结构不相等。

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

编辑:您比较许多字符串。 字符串比较在C#中进行了优化。 正如其他人所建议的,您可以在比较中更好地使用== 对于GetHashCode,也请使用其他人建议的OR ^

  1. 假设两个对象因为它们的哈希码相等而相等是错误的。 您需要分别比较所有成员
  2. 使用^而不是+组合哈希码可能更好。

感谢所有回应。 我汇总了所有答复者的反馈,并且改进后的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;
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM