5

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

我将 intersect 和 except (Linq) 与自定义 IEqualityComparer 一起使用,以查询 ISyncableUsers 的两个序列的设置差异和设置交集。

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

两个 ISyncableUser 是否相等背后的逻辑是有条件的。条件围绕两个属性 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 以便尊重上面表示的上述条件相等。我能够让 intersect 和 except 调用按预期工作的唯一方法是简单地始终从 GetHashCode() 返回相同的值,从而强制调用 Equals。

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

这行得通,但正如预期的那样,性能损失是巨大的。(我已经用非条件相等性对此进行了测试。对于两个包含 50000 个对象的集合,正确的哈希码实现允许在大约 40 毫秒内执行拦截和除外。始终返回 0 的哈希码实现大约需要 144000 毫秒(是的,2.4 分钟!) )

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

任何想法都会非常受欢迎!

4

3 回答 3

2

如果我们假设您的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 并且名称不为空。平等要求对于任何 'ISyncableUser' 对象 'u' 强制要求 u.Guid == u1.Guid,条件 u.UserPrincipalName == u1.UserPrincipalName 也应该为真。这种推理决定了 GetHashCode 的实现,对于每个用户对象,它应该基于它的名称或 guid。

于 2012-11-02T12:21:14.297 回答
2

如果我没看错,你的等式关系不是传递的。想象以下三个ISyncableUsers:

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如果它为,则为yand x; and对两个对象为xz如果它对 and 为x并且对andy也为yz

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

从另一个角度来看:您实际上是在寻找三个功能:

  • G将 GUID 映射到整数(如果您知道 GUID 但 UPN 为空白)
  • U将 UPN 映射到整数(如果您知道 UPN 但 GUID 为空白)
  • P将 (guid, upn) 对映射到整数(如果你都知道的话)

这样G(g) == U(u) == P(g, u)对于所有gu。这只有在你完全忽略的情况下才有g可能u

于 2012-11-02T14:50:17.277 回答
0

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

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

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

  • 或者,您可以在 GetHashCode 函数中维护该字典。这意味着您的 GetHashCode 函数有更多的工作要做,而且并非没有副作用。让它与多个线程或并行 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 实现具有相同的限制)

于 2012-11-02T15:13:02.963 回答