簡體   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