如何为HashSet正确实现IEqualityComparer ?

本文关键字:实现 IEqualityComparer HashSet | 更新日期: 2023-09-27 17:53:16

我正在编写一些将LDAP属性名称映射到友好名称并返回的代码。只有简单的类叫做DirectoryProperty:

public class DirectoryProperty
{
  public string Id { get; set; }
  public string Name { get; set; }
  public string HelpText {get; set; }
  public DirectoryProperty(string id, string name)
  {
    Id = id;
    Name = name;
  }
}
然后,我编写了使用HashSet构建这些对象集合的代码。我有一组固定的属性,我提供,但我想允许其他人添加自己的项目。set似乎是一个很好的结构,因为当您查询LDAP时,您不希望有重复的属性,这也适用于用户从属性列表中进行选择的UI。
public class PropertyMapper
{
  readonly HashSet<DirectoryProperty> props = new HashSet<DirectoryProperty>(new DirectoryPropertyComparer());
 public PropertyMapper() // Will eventually pass data in here
 {
   props.Add(new DirectoryProperty("displayName", "Display Name"));
   props.Add(new DirectoryProperty("displayName", "Display Name")); //err
   props.Add(new DirectoryProperty("xyz", "Profile Path")); //err
   props.Add(new DirectoryProperty("samAccountName", "User Account Name"));
   props.Add(new DirectoryProperty("mobile", "Mobile Number"));
   props.Add(new DirectoryProperty("profilePath", "Profile Path"));
 }
 public List<string> GetProperties()
 {
   return props.Select(directoryProperty => directoryProperty.Id).OrderBy(p => p).ToList();
 }
 public List<string> GetFriendlyNames()
 {
   return props.Select(directoryProperty => directoryProperty.Name).OrderBy(p => p).ToList();
 }
}

可以看到,现在构造函数中有两个有问题的数据项。其中第一个是明显的重复,另一个是基于DirectoryProperty的Name属性的重复。

我最初实现的IEqualityComparer是这样的:

class DirectoryPropertyComparer : IEqualityComparer<DirectoryProperty>
{
  public bool Equals(DirectoryProperty x, DirectoryProperty y)
  {
    if (x.Id.ToLower() == y.Id.ToLower() || x.Name.ToLower() == y.Name.ToLower())
    {
      return true;
    }
    return false;
  }
  public int GetHashCode(DirectoryProperty obj)
  {
    return (obj.Id.Length ^ obj.Name.Length).GetHashCode();
  }
}

我能做些什么来确保Id和DirectoryProperty的Name属性都被检查唯一性,以确保基于任何一个的重复被捕获?我可能在这里太严格了,我住在我现有的代码,因为它似乎处理重复Id的OK,但我有兴趣了解更多关于这个。

如何为HashSet正确实现IEqualityComparer ?

不清楚你到底想做什么:

  • 你的等式方法认为两个值相等,如果它们的名字它们的id相同
  • 你的GetHashCode方法包括两个值,所以(意外冲突除外)你只匹配名称和ID在两个对象
  • 中具有相同的长度

从根本上说,第一种方法是有缺陷的。考虑三个条目:

A = { Name=¨N1¨, ID=¨ID1¨ }
B = { Name=¨N2¨, ID=¨ID1¨ }
C = { Name=¨N2¨, ID=¨ID2¨ }

您似乎想要:

A.Equals(B) - true
B.Equals(C) - true
A.Equals(C) - false

这违反了相等性(及物性)规则。

强烈建议你有两个集合——一个按ID比较值,另一个按Name比较值。然后编写一个方法,只在中没有出现的情况下才向两个集合添加条目。

这种方法是行不通的。Equals方法需要定义一个等价关系,而这种关系不能用这种方式定义。

等价关系必须是可传递的,但这个关系不是可传递的。

{"A", "B"} == {"C", "B"}
and
{"A", "B"} == {"A", "D"}
but
{"C", "B"} != {"A", "D"}

更好的方法是创建两个字典——一个用于ID,一个用于name——并在添加新值之前检查两个字典是否存在冲突。