从列表C#中删除重复项

我正在关注stackoverflow上的一篇关于从C#中的List中删除重复项的post 。

如果是某些用户定义的类型,例如:

 class Contact { public string firstname; public string lastname; public string phonenum; } 

建议的(HashMap)不会删除重复。 我想,我必须重新定义一些比较两个对象的方法,不是吗?

HashSet 删除重复项,因为它是一个集合…但仅当您的类型适当地定义相等时。

我怀疑“复制”你的意思是“一个具有相等字段值的对象到另一个对象” – 你需要重写Equals / GetHashCode才能工作,和/或实现IEquatable …或者你可以提供IEqualityComparerHashSet构造函数。

可以只调用Distinct LINQ扩展方法,而不是使用HashSet 。 例如:

 list = list.Distinct().ToList(); 

但同样,你需要以某种方式或其他方式提供适当的平等定义。

这是一个示例实现。 注意我是如何使它变为不可变的(对于可变类型,相等是奇数,因为两个对象可以等于一分钟而下一个不相等)并且使用公共属性使字段成为私有。 最后,我已经密封了类 – 通常应该密封不可变类型,这使得更容易讨论平等。

 using System; using System.Collections.Generic; public sealed class Contact : IEquatable { private readonly string firstName; public string FirstName { get { return firstName; } } private readonly string lastName; public string LastName { get { return lastName; } } private readonly string phoneNumber; public string PhoneNumber { get { return phoneNumber; } } public Contact(string firstName, string lastName, string phoneNumber) { this.firstName = firstName; this.lastName = lastName; this.phoneNumber = phoneNumber; } public override bool Equals(object other) { return Equals(other as Contact); } public bool Equals(Contact other) { if (object.ReferenceEquals(other, null)) { return false; } if (object.ReferenceEquals(other, this)) { return true; } return FirstName == other.FirstName && LastName == other.LastName && PhoneNumber == other.PhoneNumber; } public override int GetHashCode() { // Note: *not* StringComparer; EqualityComparer // copes with null; StringComparer doesn't. var comparer = EqualityComparer.Default; // Unchecked to allow overflow, which is fine unchecked { int hash = 17; hash = hash * 31 + comparer.GetHashCode(FirstName); hash = hash * 31 + comparer.GetHashCode(LastName); hash = hash * 31 + comparer.GetHashCode(PhoneNumber); return hash; } } } 

编辑:好的,回应GetHashCode()实现的解释请求:

  • 我们想要组合此对象的属性的哈希码
  • 我们没有在任何地方检查无效,所以我们应该假设它们中的一些可能为空。 EqualityComparer.Default总是处理这个,这很好……所以我用它来获取每个字段的哈希码。
  • 将几个哈希码组合成一个的“加法和乘法”方法是Josh Bloch推荐的标准方法。 还有很多其他的通用哈希算法,但是这个算法适用于大多数应用程序。
  • 我不知道你是否默认在一个已检查的上下文中编译,所以我把计算放在一个未经检查的上下文中。 我们真的不在乎重复的乘法/加法是否会导致溢出,因为我们不是在寻找一个“幅度”……只是我们可以为相同的物体重复到达的数字。

顺便提一下,有两种处理无效的替代方法:

 public override int GetHashCode() { // Unchecked to allow overflow, which is fine unchecked { int hash = 17; hash = hash * 31 + (FirstName ?? "").GetHashCode(); hash = hash * 31 + (LastName ?? "").GetHashCode(); hash = hash * 31 + (PhoneNumber ?? "").GetHashCode(); return hash; } } 

要么

 public override int GetHashCode() { // Unchecked to allow overflow, which is fine unchecked { int hash = 17; hash = hash * 31 + (FirstName == null ? 0 : FirstName.GetHashCode()); hash = hash * 31 + (LastName == null ? 0 : LastName.GetHashCode()); hash = hash * 31 + (PhoneNumber == null ? 0 : PhoneNumber.GetHashCode()); return hash; } } 
 class Contact { public int Id { get; set; } public string Name { get; set; } public override string ToString() { return string.Format("{0}:{1}", Id, Name); } static private IEqualityComparer comparer; static public IEqualityComparer Comparer { get { return comparer ?? (comparer = new EqualityComparer()); } } class EqualityComparer : IEqualityComparer { bool IEqualityComparer.Equals(Contact x, Contact y) { if (x == y) return true; if (x == null || y == null) return false; return x.Name == y.Name; // let's compare by Name } int IEqualityComparer.GetHashCode(Contact c) { return c.Name.GetHashCode(); // let's compare by Name } } } class Program { public static void Main() { var list = new List { new Contact { Id = 1, Name = "John" }, new Contact { Id = 2, Name = "Sylvia" }, new Contact { Id = 3, Name = "John" } }; var distinctNames = list.Distinct(Contact.Comparer).ToList(); foreach (var contact in distinctNames) Console.WriteLine(contact); } } 

 1:John 2:Sylvia 

对于这项任务,我不一定认为实施IComparable是一个明显的解决方案。 您可能希望以多种不同方式对唯一性进行排序和测试。

我赞成实现IEqualityComparer

 sealed class ContactFirstNameLastNameComparer : IEqualityComparer { public bool Equals (Contact x, Contact y) { return x.firstname == y.firstname && x.lastname == y.lastname; } public int GetHashCode (Contact obj) { return obj.firstname.GetHashCode () ^ obj.lastname.GetHashCode (); } } 

然后使用System.Linq.Enumerable.Distinct (假设您至少使用.NET 3.5)

 var unique = contacts.Distinct (new ContactFirstNameLastNameComparer ()).ToArray (); 

PS。 说到HashSet<>注意, HashSet<>IEqualityComparer<>作为构造函数参数。