3

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

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

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

我知道如何用一个属性来做到这一点:

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

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

4

2 回答 2

6

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

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

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

于 2012-04-17T13:38:54.383 回答
4

另一种方法是将 LINQDistinct扩展方法与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);

更新

我建议使用在构造函数中接受 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);
于 2012-04-17T14:00:50.833 回答