2012-04-17 225 views
2

我有一个C#中的对象列表。所有的对象都包含属性dept和course。
有几个对象具有相同的部门和课程。从列表中删除具有重复属性的对象

如何修剪列表(或创建新列表),其中每个唯一的对象(部分&当然)属性只有一个对象。

[任何额外的副本都跌出名单]

我知道如何与一个单一的属性来完成这个:

fooList.GroupBy(x => x.dept).Select(x => x.First()); 

不过,我想知道如何为多个属性做到这一点( 2或更多)?

+0

请问您的列表需要可排序? – 2012-04-17 13:39:15

+0

我到达这一点时已经对列表进行了排序。 – Baxter 2012-04-17 13:41:54

回答

5

要使用多个属性,你可以使用匿名类型:

var query = fooList.GroupBy(x => new { x.Dept, x.Course }) 
        .Select(x => x.First()); 

当然,这要看是什么类型的DeptCourse是确定的平等。或者,您的课程可以实施IEqualityComparer<T>,然后您可以使用接受比较器的Enumerable.Distinct method

+0

部门和课程均为整数。 – Baxter 2012-04-17 13:44:40

+0

@Baxter然后这种方法将工作得很好。 – 2012-04-17 13:53:05

+0

这似乎是伎俩!然而,我想知道,因为我使用的是匿名类型,我怎样才能传递“var query”? 什么样的方法签名可以接受?还是有某种转换我会返回到它的原始类型等? – Baxter 2012-04-17 19:06:13

2

另一种方法是用IEqualityComparer<Foo>一起使用LINQ Distinct扩展方法。它要求你实现一个比较器;然而,后者是可重用和可测试的。

public class FooDeptCourseEqualityComparer : IEqualityComparer<Foo> 
{ 
    public bool Equals(Foo x, Foo y) 
    { 
     return 
      x.Dept == y.Dept && 
      x.Course.ToLower() == y.Course.ToLower(); 
    } 

    public int GetHashCode(Foo obj) 
    { 
     unchecked { 
      return 527 + obj.Dept.GetHashCode() * 31 + obj.Course.GetHashCode(); 
     } 
    } 

    #region Singleton Pattern 

    public static readonly FooDeptCourseEqualityComparer Instance = 
     new FooDeptCourseEqualityComparer(); 

    private FooDeptCourseEqualityComparer() { } 

    #endregion 
} 

我的例子使用单例模式。由于该类没有任何状态信息,因此每次我们使用它时都不需要创建新的实例。

我的代码不处理null值。当然,如果可能发生,你将不得不处理它们。

独特的值返回这样

var result = fooList.Distinct(FooDeptCourseEqualityComparer.Instance); 

UPDATE

我建议使用接受lambda表达式在构造函数中,可以在多个场合重复使用一个通用的EqualityComparer类

public class LambdaEqualityComparer<T> : IEqualityComparer<T> 
{ 
    private Func<T, T, bool> _areEqual; 
    private Func<T, int> _getHashCode; 

    public LambdaEqualityComparer(Func<T, T, bool> areEqual, 
            Func<T, int> getHashCode) 
    { 
     _areEqual = areEqual; 
     _getHashCode = getHashCode; 
    } 

    public LambdaEqualityComparer(Func<T, T, bool> areEqual) 
     : this(areEqual, obj => obj.GetHashCode()) 
    { 
    } 

    #region IEqualityComparer<T> Members 

    public bool Equals(T x, T y) 
    { 
     return _areEqual(x, y); 
    } 

    public int GetHashCode(T obj) 
    { 
     return _getHashCode(obj); 
    } 

    #endregion 
} 

您可以使用它像这样

var comparer = new LambdaEqualityComparer<Foo>(
    (x, y) => x.Dept == y.Dept && x.Course == y.Course, 
    obj => { 
     unchecked { 
      return 527 + obj.Dept.GetHashCode() * 31 + obj.Course.GetHashCode(); 
     } 
    } 
); 

var result = fooList.Distinct(comparer); 

注意:必须提供的哈希码的计算,因为Distinct使用内部Set<T>类,后者又使用散列码。


更新#2

甚至更​​通用的相等比较器自动执行所述比较和接受属性访问的列表;但是,您无法控制比较的执行方式。

public class AutoEqualityComparer<T> : IEqualityComparer<T> 
{ 
    private Func<T, object>[] _propertyAccessors; 

    public AutoEqualityComparer(params Func<T, object>[] propertyAccessors) 
    { 
     _propertyAccessors = propertyAccessors; 
    } 

    #region IEqualityComparer<T> Members 

    public bool Equals(T x, T y) 
    { 
     foreach (var getProp in _propertyAccessors) { 
      if (!getProp(x).Equals(getProp(y))) { 
       return false; 
      } 
     } 
     return true; 
    } 

    public int GetHashCode(T obj) 
    { 
     unchecked { 
      int hash = 17; 
      foreach (var getProp in _propertyAccessors) { 
       hash = hash * 31 + getProp(obj).GetHashCode(); 
      } 
      return hash; 
     } 
    } 

    #endregion 
} 

使用

var comparer = new AutoEqualityComparer<Foo>(foo => foo.Dept, 
              foo => foo.Course); 
var result = fooList.Distinct(comparer);