繁体   English   中英

为IEqualityComparer实现GetHashCode <T> 条件平等

[英]Implementing GetHashCode for IEqualityComparer<T> with conditional equality

我想知道是否有人对此问题有任何建议。

我使用带有自定义IEqualityComparer的intersect和except(Linq)来查询设置差异并设置两个ISyncableUsers序列的交集。

public interface ISyncableUser
{
    string Guid { get; }
    string UserPrincipalName { get; }
}

两个ISyncableUsers是否相等的逻辑是有条件的。 条件围绕两个属性Guid和UserPrincipalName中的任何一个是否具有值。 解释这种逻辑的最好方法是使用代码。 下面是我的客户IEqualityComparer的Equals方法的实现。

public bool Equals(ISyncableUser userA, ISyncableUser userB)
{
    if (userA == null && userB == null)
    {
        return true;
    }

    if (userA == null)
    {
        return false;
    }

    if (userB == null)
    {
        return false;
    }

    if ((!string.IsNullOrWhiteSpace(userA.Guid) && !string.IsNullOrWhiteSpace(userB.Guid)) &&
        userA.Guid == userB.Guid)
    {
        return true;
    }

    if (UsersHaveUpn(userA, userB))
    {
        if (userB.UserPrincipalName.Equals(userA.UserPrincipalName, StringComparison.InvariantCultureIgnoreCase))
        {
            return true;
        }
    }
    return false;
}

private bool UsersHaveUpn(ISyncableUser userA, ISyncableUser userB)
{
    return !string.IsNullOrWhiteSpace(userA.UserPrincipalName)
            && !string.IsNullOrWhiteSpace(userB.UserPrincipalName);
}

我遇到的问题是实现GetHashCode,以便尊重上面表示的上述条件相等。 我能够获得交叉的唯一方法是,除了调用按预期工作之外,简单总是从GetHashCode()返回相同的值,强制调用Equals。

 public int GetHashCode(ISyncableUser obj)
 {
     return 0;
 }

这可行,但性能损失是巨大的,如预期的那样。 (我已经用非条件相等测试了这个。有两个包含50000个对象的集合,一个正确的哈希码实现允许执行拦截,除了大约40ms。总是返回0的哈希码实现大约需要144000ms(是的,2.4分钟!) )

那么,我将如何在上面的场景中实现GetHashCode()呢?

任何想法都会受到欢迎!

如果我们假设您的Equals实现是正确的,即它是反射的,传递的和对称的,那么GetHashCode函数的基本实现应该如下所示:


        public int GetHashCode(ISyncableUser obj)
        {
            if (obj == null)
            {
                return SOME_CONSTANT;
            }

            if (!string.IsNullOrWhiteSpace(obj.UserPrincipalName) &&
                <can have user object with different guid and the same name>)
            {
                return GetHashCode(obj.UserPrincipalName);
            }

            return GetHashCode(obj.Guid);
        }

您还应该了解您的对象之间存在相当复杂的依赖关系。

实际上,让我们使用两个ISyncableUser对象:'u1'和'u2',例如u1.Guid!= u2.Guid,但u1.UserPrincipalName == u2.UserPrincipalName和名称不为空。 Equality的要求强制要求任何'ISyncableUser'对象'u'使得u.Guid == u1.Guid,条件u.UserPrincipalName == u1.UserPrincipalName也应该为真。 这个推理规定了GetHashCode实现,对于每个用户对象,它应该基于它的名称或guid。

如果我正确地读到这个,你的平等关系就不会传递。 ISyncableUser以下三个ISyncableUser

A { Guid: "1", UserPrincipalName: "2" }
B { Guid: "2", UserPrincipalName: "2" }
C { Guid: "2", UserPrincipalName: "1" }
  • A == B因为它们具有相同的UserPrincipalName
  • B == C因为他们有相同的Guid
  • A != C因为它们不共享。

规格来看,

Equals方法是自反,对称和传递的。 也就是说,如果用于将对象与自身进行比较,则返回true ; 两个对象xy如果它是真正的 yx ; 和两个物体真实 xz如果是真正的 xy亦是如此 yz

如果您的相等关系不一致,则无法实现备份它的哈希代码。

从另一个角度来看:你基本上在寻找三个功能:

  • G将GUID映射到int(如果您知道GUID但UPN为空)
  • U将UPN映射到int(如果您知道UPN但GUID为空)
  • P映射(guid,upn)与int对(如果你知道两者)

对于所有guG(g) == U(u) == P(g, u) 只有完全忽略gu才能实现这一点。

一种方法是维护用户名和GUIDS的哈希码字典。

  • 您可以在开始时为所有用户生成此字典,这可能是最干净的解决方案。

  • 您可以在每个用户的构造函数中添加或更新条目。

  • 或者,您可以在GetHashCode函数中维护该字典。 这意味着您的GetHashCode函数还有更多工作要做,并且没有副作用。 要使用多个线程或parallel-linq,需要更仔细的工作。 所以我不知道我是否会推荐这种方法。

不过,这是我的尝试:

private Dictionary<string, int> _guidHash = 
     new Dictionary<string, int>();

private Dictionary<string, int> _nameHash = 
     new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

public int GetHashCode(ISyncableUser obj)
{
    int hash = 0;

    if (obj==null) return hash;

    if (!String.IsNullOrWhiteSpace(obj.Guid) 
        && _guidHash.TryGetValue(obj.Guid, out hash))
        return hash;

    if (!String.IsNullOrWhiteSpace(obj.UserPrincipalName) 
        && _nameHash.TryGetValue(obj.UserPrincipalName, out hash))
        return hash;

    hash = RuntimeHelpers.GetHashCode(obj); 
    // or use some other method to generate an unique hashcode here

    if (!String.IsNullOrWhiteSpace(obj.Guid)) 
         _guidHash.Add(obj.Guid, hash);

    if (!String.IsNullOrWhiteSpace(obj.UserPrincipalName)) 
         _nameHash.Add(obj.UserPrincipalName, hash);

    return hash;
}

请注意,如果ISyncableUser对象不能很好地显示并且出现像Rawling的答案中的情况,这将失败。 我假设具有相同GUID的用户将具有相同的名称或根本没有名称,具有相同principalName的用户具有相同的GUID或根本没有GUID。 (我认为给定的Equals实现具有相同的局限性)

暂无
暂无

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

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