[英]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 ; 真兩個對象
x
和y
如果它是真正的y
和x
; 和兩個物體真實x
和z
如果是真正的x
和y
並亦是如此y
和z
。
如果您的相等關系不一致,則無法實現備份它的哈希代碼。
從另一個角度來看:你基本上在尋找三個功能:
G
將GUID映射到int(如果您知道GUID但UPN為空) U
將UPN映射到int(如果您知道UPN但GUID為空) P
映射(guid,upn)與int對(如果你知道兩者) 對於所有g
和u
, G(g) == U(u) == P(g, u)
。 只有完全忽略g
和u
才能實現這一點。
一種方法是維護用戶名和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.