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

108

我有一个在C#中的对象列表,所有对象都包含一个ID属性。

有几个对象具有相同的ID属性。

如何对列表进行去重(或创建一个新的列表),以便每个ID属性只保留一个对象?

[任何额外的重复项都将从列表中删除]

5个回答

225

如果你想避免使用第三方库,你可以采取以下做法:

var bar = fooArray.GroupBy(x => x.Id).Select(x => x.First()).ToList();

这将按Id属性对数组进行分组,然后选择该分组中的第一个条目。


11
这段代码在这里完美运行:List<InputRow> uniqueRows = inputRows.GroupBy(x => x.Id).Select(x => x.First()).ToList<InputRow>(); - Baxter
7
很高兴能帮忙!需要说明的是,你在ToList()上的<InputRow>是多余的。你只需要用.ToList()就可以了。 - Daniel Mann
1
你是对的,只需要使用ToList()而不是ToList<InputRow>()就可以了。 - Baxter
一个比尝试弄清为什么使用Distinct和IQuatable不起作用更好的选择。 - Ariwibawa

36

使用MoreLINQ的DistinctBy()方法即可实现,它允许使用对象属性进行去重。不幸的是,内置的LINQ Distinct()方法不够灵活。

var uniqueItems = allItems.DistinctBy(i => i.Id);

DistinctBy()

通过投影和投影类型的默认相等比较器,返回给定源的所有不同元素。

PS:感谢Jon Skeet与社区分享此库。


1
我认为这是一个很好的解决方案,但我正在尝试避免使用第三方库。谢谢。 - Baxter
3
很幸运,你可以看到它是如何实施的。 - sll

12
从.NET 6开始,有一个新的DistinctBy LINQ操作符可用:
public static IEnumerable<TSource> DistinctBy<TSource,TKey> (
    this IEnumerable<TSource> source,
    Func<TSource,TKey> keySelector);

根据指定的键选择函数从序列中返回不同的元素。
使用示例:
List<Item> distinctList = listWithDuplicates
    .DistinctBy(i => i.Id)
    .ToList();

还有一个重载,它带有一个 IEqualityComparer<TKey> 参数。


原地更新: 如果不想创建一个新的 List<T>,则可以使用 List<T> 类的 RemoveDuplicates 扩展方法:

/// <summary>
/// Removes all the elements that are duplicates of previous elements,
/// according to a specified key selector function.
/// </summary>
/// <returns>
/// The number of elements removed.
/// </returns>
public static int RemoveDuplicates<TSource, TKey>(
    this List<TSource> source,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> keyComparer = null)
{
    ArgumentNullException.ThrowIfNull(source);
    ArgumentNullException.ThrowIfNull(keySelector);
    HashSet<TKey> hashSet = new(keyComparer);
    return source.RemoveAll(item => !hashSet.Add(keySelector(item)));
}

这种方法很高效(O(n)),但也有一定危险性,因为它基于可能会破坏数据的 List<T>.RemoveAll 方法¹。如果 keySelector lambda 函数对某些元素成功执行后又对另一个元素执行失败,则部分修改过的 List<T> 既不会恢复到其初始状态,也不会处于可以识别为成功执行单个 Remove 的状态。相反,它将转换为包含重复现有元素的已损坏状态。所以,如果 keySelector lambda 函数不是完全可靠的,则应在 try 块中调用 RemoveDuplicates 方法,并在 catch 块中丢弃可能已经损坏的列表。

或者您可以使用安全的自定义实现替换危险的内置RemoveAll,以提供可预测的行为。

¹ 适用于所有 .NET 版本和平台,包括最新的 .NET 7。我已在 GitHub 上提交了一个建议,以记录List<T>.RemoveAll方法的破坏性行为,而我收到的反馈是既不应记录该行为,也不应修复实现。


7
var list = GetListFromSomeWhere();
var list2 = GetListFromSomeWhere();
list.AddRange(list2);

....
...
var distinctedList = list.DistinctBy(x => x.ID).ToList();

More LINQGitHub 上提供。

或者,如果您出于某种原因不想使用外部 dll,您可以使用此 Distinct 重载:

public static IEnumerable<TSource> Distinct<TSource>(
    this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)

使用方法:

public class FooComparer : IEqualityComparer<Foo>
{
    // Products are equal if their names and product numbers are equal.
    public bool Equals(Foo x, Foo y)
    {

        //Check whether the compared objects reference the same data.
        if (Object.ReferenceEquals(x, y)) return true;

        //Check whether any of the compared objects is null.
        if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
            return false;

        return x.ID == y.ID
    }
}



list.Distinct(new FooComparer());

3

不确定是否还有人在寻找其他方法来完成此操作。但是,我使用了以下代码根据匹配的ID号从用户对象列表中删除重复项。

private ArrayList RemoveSearchDuplicates(ArrayList SearchResults)
{
    ArrayList TempList = new ArrayList();

    foreach (User u1 in SearchResults)
    {
        bool duplicatefound = false;
        foreach (User u2 in TempList)
            if (u1.ID == u2.ID)
                duplicatefound = true;

        if (!duplicatefound)
            TempList.Add(u1);
    }
    return TempList;
}

调用:SearchResults = RemoveSearchDuplicates(SearchResults);

这行代码涉及到搜索结果的去重,它会调用名为“RemoveSearchDuplicates”的函数来实现。

2
这是毫无意义的O(n ^2),而常规的GroupBy只是O(n)... - Alexei Levenkov

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