我想知道是否有人对此问题有任何建议。
我使用带有自定义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()?
任何想法都会受到欢迎!
答案 0 :(得分: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',这样u1Guid!= u2.Guid,但u1.UserPrincipalName == u2.UserPrincipalName和名称不为空。 Equality的要求强制要求任何'ISyncableUser'对象'u'使得u.Guid == u1.Guid,条件u.UserPrincipalName == u1.UserPrincipalName也应该为真。这个推理规定了GetHashCode实现,对于每个用户对象,它应该基于它的名称或guid。
答案 1 :(得分:2)
如果我正确地读到这个,你的平等关系就不会传递。想象以下三个ISyncableUser
:
A { Guid: "1", UserPrincipalName: "2" }
B { Guid: "2", UserPrincipalName: "2" }
C { Guid: "2", UserPrincipalName: "1" }
A == B
因为它们具有相同的UserPrincipalName
B == C
因为它们具有相同的Guid
A != C
因为他们不共享。来自the spec,
等于方法具有反身性,对称性和传递性。也就是说,如果用于将对象与自身进行比较,则返回 true ;如果<{1}}和
x
的为真,则{strong> true 为y
和y
两个对象;对于x
和x
以及<{1}}和<{1}} 为真,对于z
和x
两个对象 true {true} <{1}}和y
。
如果你的等式关系不一致,你就无法实现支持它的哈希码。
从另一个角度来看:你基本上在寻找三个功能:
y
将GUID映射到整数(如果您知道GUID但UPN为空白)z
将UPN映射到整数(如果你知道UPN但GUID是空白的)G
映射(guid,upn)对与int(如果你们两个都知道)所有U
和P
G(g) == U(u) == P(g, u)
。只有在完全忽略g
和u
时才可以这样做。
答案 2 :(得分:0)
一种方法是维护用户名和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实现具有相同的限制)