简体   繁体   English

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

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

I'm wondering if anyone as any suggestions for this problem. 我想知道是否有人对此问题有任何建议。

I'm using intersect and except (Linq) with a custom IEqualityComparer in order to query the set differences and set intersections of two sequences of ISyncableUsers. 我使用带有自定义IEqualityComparer的intersect和except(Linq)来查询设置差异并设置两个ISyncableUsers序列的交集。

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

The logic behind whether two ISyncableUsers are equal is conditional. 两个ISyncableUsers是否相等的逻辑是有条件的。 The conditions center around whether either of the two properties, Guid and UserPrincipalName, have values. 条件围绕两个属性Guid和UserPrincipalName中的任何一个是否具有值。 The best way to explain this logic is with code. 解释这种逻辑的最好方法是使用代码。 Below is my implementation of the Equals method of my customer IEqualityComparer. 下面是我的客户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);
}

The problem I'm having, is with implementing GetHashCode so that the above conditional equality, represented above, is respected. 我遇到的问题是实现GetHashCode,以便尊重上面表示的上述条件相等。 The only way I've been able to get the intersect and except calls to work as expected is to simple always return the same value from GetHashCode(), forcing a call to Equals. 我能够获得交叉的唯一方法是,除了调用按预期工作之外,简单总是从GetHashCode()返回相同的值,强制调用Equals。

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

This works but the performance penalty is huge, as expected. 这可行,但性能损失是巨大的,如预期的那样。 (I've tested this with non-conditional equality. With two sets containing 50000 objects, a proper hashcode implementation allows execution of intercept and except in about 40ms. A hashcode implementation that always returns 0 takes approximately 144000ms (yes, 2.4 minutes!)) (我已经用非条件相等测试了这个。有两个包含50000个对象的集合,一个正确的哈希码实现允许执行拦截,除了大约40ms。总是返回0的哈希码实现大约需要144000ms(是的,2.4分钟!) )

So, how would I go about implementing a GetHashCode() in the scenario above? 那么,我将如何在上面的场景中实现GetHashCode()呢?

Any thoughts would be more than welcome! 任何想法都会受到欢迎!

If we suppose that your Equals implementation is correct, ie it's reflective, transitive and symmetric then the basic implementation for your GetHashCode function should look like this: 如果我们假设您的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);
        }

You should also understand that you've got rather intricate dependencies between your objects. 您还应该了解您的对象之间存在相当复杂的依赖关系。

Indeed, let's take two ISyncableUser objects: 'u1' and 'u2', such that u1.Guid != u2.Guid, but u1.UserPrincipalName == u2.UserPrincipalName and names are not empty. 实际上,让我们使用两个ISyncableUser对象:'u1'和'u2',例如u1.Guid!= u2.Guid,但u1.UserPrincipalName == u2.UserPrincipalName和名称不为空。 Requirements for Equality imposes that for any 'ISyncableUser' object 'u' such that u.Guid == u1.Guid, the condition u.UserPrincipalName == u1.UserPrincipalName should be also true. Equality的要求强制要求任何'ISyncableUser'对象'u'使得u.Guid == u1.Guid,条件u.UserPrincipalName == u1.UserPrincipalName也应该为真。 This reasoning dictates GetHashCode implementation, for each user object it should be based either on it's name or guid. 这个推理规定了GetHashCode实现,对于每个用户对象,它应该基于它的名称或guid。

If I'm reading this correctly, your equality relation is not transitive. 如果我正确地读到这个,你的平等关系就不会传递。 Picture the following three ISyncableUser s: ISyncableUser以下三个ISyncableUser

A { Guid: "1", UserPrincipalName: "2" }
B { Guid: "2", UserPrincipalName: "2" }
C { Guid: "2", UserPrincipalName: "1" }
  • A == B because they have the same UserPrincipalName A == B因为它们具有相同的UserPrincipalName
  • B == C because they have the same Guid B == C因为他们有相同的Guid
  • A != C because they don't share either. A != C因为它们不共享。

From the spec , 规格来看,

The Equals method is reflexive, symmetric, and transitive. Equals方法是自反,对称和传递的。 That is, it returns true if used to compare an object with itself; 也就是说,如果用于将对象与自身进行比较,则返回true ; true for two objects x and y if it is true for y and x ; 两个对象xy如果它是真正的 yx ; and true for two objects x and z if it is true for x and y and also true for y and z . 和两个物体真实 xz如果是真正的 xy亦是如此 yz

If your equality relation isn't consistent, there's no way you can implement a hash code that backs it up. 如果您的相等关系不一致,则无法实现备份它的哈希代码。

From another point of view: you're essentially looking for three functions: 从另一个角度来看:你基本上在寻找三个功能:

  • G mapping GUIDs to ints (if you know the GUID but the UPN is blank) G将GUID映射到int(如果您知道GUID但UPN为空)
  • U mapping UPNs to ints (if you know the UPN but the GUID is blank) U将UPN映射到int(如果您知道UPN但GUID为空)
  • P mapping (guid, upn) pairs to ints (if you know both) P映射(guid,upn)与int对(如果你知道两者)

such that G(g) == U(u) == P(g, u) for all g and u . 对于所有guG(g) == U(u) == P(g, u) This is only possible if you ignore g and u completely. 只有完全忽略gu才能实现这一点。

One way would be to maintain a dictionary of hashcodes for usernames and GUIDS. 一种方法是维护用户名和GUIDS的哈希码字典。

  • You could generate this dictionary at the start once for all users, which would probably the cleanest solution. 您可以在开始时为所有用户生成此字典,这可能是最干净的解决方案。

  • You could add or update an entry in the Constructor of each user. 您可以在每个用户的构造函数中添加或更新条目。

  • Or, you could maintain that dictionary inside the GetHashCode function. 或者,您可以在GetHashCode函数中维护该字典。 This means your GetHashCode function has more work to do and is not free of side-effects. 这意味着您的GetHashCode函数还有更多工作要做,并且没有副作用。 Getting this to work with multiple threads or parallel-linq will need some more carefull work. 要使用多个线程或parallel-linq,需要更仔细的工作。 So I don't know whether I would recommend this approach. 所以我不知道我是否会推荐这种方法。

Nevertheless, here is my attempt: 不过,这是我的尝试:

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

Note that this will fail if the ISyncableUser objects do not play nice and exhibit cases like in Rawling's answer. 请注意,如果ISyncableUser对象不能很好地显示并且出现像Rawling的答案中的情况,这将失败。 I am assuming that users with the same GUID will have the same name or no name at all, and users with the same principalName have the same GUID or no GUID at all. 我假设具有相同GUID的用户将具有相同的名称或根本没有名称,具有相同principalName的用户具有相同的GUID或根本没有GUID。 (I think the given Equals implementation has the same limitations) (我认为给定的Equals实现具有相同的局限性)

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

相关问题 通用 IEqualityComparer<T> 和 GetHashCode - Generic IEqualityComparer<T> and GetHashCode 实现 IEqualityComparer 时 GetHashCode 是否应该检查 null? - When Implementing IEqualityComparer Should GetHashCode check for null? 为什么IEqualityComparer <T> 有GetHashCode()方法? - Why IEqualityComparer<T> has GetHashCode() method? GetHashCode在IEqualityComparer中的作用是什么<T>在.NET 中? - What's the role of GetHashCode in the IEqualityComparer<T> in .NET? 利用IEqualityComparer的GetHashCode()部分 <T> 直接比较? - Utilizing the GetHashCode() part of IEqualityComparer<T> for direct comparisons? 实现IEqualityComparer会修改相等的测试结果吗? - Implementing IEqualityComparer modifies equality test results? 实施IEqualityComparer时 <T> .GetHashCode(T obj),我可以使用当前实例的状态,还是必须使用obj? - When implementing IEqualityComparer<T>.GetHashCode(T obj), can I use the current instance's state, or do I have to use obj? 测试对象相等性的最佳方法是什么 - 不重写Equals和GetHashCode,或实现IEquatable <T> ? - What is the best way to test for object equality - without overriding Equals & GetHashCode, or implementing IEquatable<T>? 使用具有公差的IEqualityComparer GetHashCode - Using IEqualityComparer GetHashCode with a tolerance GetHashCode Equality - GetHashCode Equality
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM