从List中删除具有重复属性的对象

时间:2012-04-17 13:34:42

标签: c# linq list

我在C#中有一个对象列表。所有对象都包含属性部门和课程 有几个对象具有相同的部门和课程。

如何修剪List(或创建一个新List),其中每个唯一(dept& course)属性只有一个对象。

[从列表中删除任何其他重复项]

我知道如何使用单个属性执行此操作:

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

但是,我想知道如何为多个属性(2个或更多)执行此操作?

2 个答案:

答案 0 :(得分:5)

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

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

当然,这取决于DeptCourse确定相等的类型。或者,您的类可以实现IEqualityComparer<T>,然后您可以使用接受比较器的Enumerable.Distinct method

答案 1 :(得分:3)

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

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);

更新

我建议使用一个通用的EqualityComparer类,它在构造函数中接受lambda表达式,并且可以在多种情况下重用

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);