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

3
我有一个 C# 对象列表,所有对象都包含 dept 和 course 属性。有几个对象的 dept 和 course 属性相同。
如何修整该列表(或创建一个新列表),以使每个唯一 (dept & course) 属性只存在一个对象。
[任何额外的重复将从列表中删除]
我知道如何使用单个属性来完成此操作:
fooList.GroupBy(x => x.dept).Select(x => x.First());

然而,我想知道如何对多个属性(2个或更多)进行此操作?

你的列表需要可排序吗? - Dave S
我已经在到达这个点之前按时间对列表进行了排序。 - Baxter
2个回答

6

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

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

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

部门和课程都是整型。 - Baxter
似乎这样做就可以了!但是,我在想,既然我正在使用匿名类型,那么我该如何传递“var query”? 哪种方法签名可以接受它?还是有一些转换可以使其返回原始类型等等? - Baxter
@ Baxter 上面的查询返回一个 IEnumerable <T>,其中 T 是在 fooList 中使用的原始类。匿名类型仅用于分组; 选择语句进行的最终投影是原始类。对于此查询,您将其传递给任何接受 IEnumerable <T> 的东西。如果需要列表,请在查询结尾处添加 .ToList() - Ahmad Mageed
我可以做一些简单的事情,比如:List<OriginalType> myGroupedList = query.ToList(); - Baxter
1
这对我帮助很大,我最初使用foreach进行比较,一个包含250k个对象的列表需要75秒才能运行。使用这个解决方案后,同样的列表现在只需要0.12秒就可以完成。 - Richard

4
另一种方法是使用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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接