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

本文关键字:属性 对象 列表 删除 | 更新日期: 2023-09-27 18:31:27

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

如何修剪列表(或创建一个新列表),其中每个唯一(部门和课程)属性只有一个对象。

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

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

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

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

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

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

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

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

另一种方法是将 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);
相关文章: