我有一个在C#中的对象列表,所有对象都包含一个ID属性。
有几个对象具有相同的ID属性。
如何对列表进行去重(或创建一个新的列表),以便每个ID属性只保留一个对象?
[任何额外的重复项都将从列表中删除]
如果你想避免使用第三方库,你可以采取以下做法:
var bar = fooArray.GroupBy(x => x.Id).Select(x => x.First()).ToList();
这将按Id属性对数组进行分组,然后选择该分组中的第一个条目。
使用MoreLINQ的DistinctBy()
方法即可实现,它允许使用对象属性进行去重。不幸的是,内置的LINQ Distinct()
方法不够灵活。
var uniqueItems = allItems.DistinctBy(i => i.Id);
DistinctBy()
通过投影和投影类型的默认相等比较器,返回给定源的所有不同元素。
PS:感谢Jon Skeet与社区分享此库。
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)));
}
List<T>.RemoveAll
方法¹。如果 keySelector
lambda 函数对某些元素成功执行后又对另一个元素执行失败,则部分修改过的 List<T>
既不会恢复到其初始状态,也不会处于可以识别为成功执行单个 Remove
的状态。相反,它将转换为包含重复现有元素的已损坏状态。所以,如果 keySelector
lambda 函数不是完全可靠的,则应在 try
块中调用 RemoveDuplicates
方法,并在 catch
块中丢弃可能已经损坏的列表。
或者您可以使用安全的自定义实现替换危险的内置RemoveAll
,以提供可预测的行为。
¹ 适用于所有 .NET 版本和平台,包括最新的 .NET 7。我已在 GitHub 上提交了一个建议,以记录List<T>.RemoveAll
方法的破坏性行为,而我收到的反馈是既不应记录该行为,也不应修复实现。
var list = GetListFromSomeWhere();
var list2 = GetListFromSomeWhere();
list.AddRange(list2);
....
...
var distinctedList = list.DistinctBy(x => x.ID).ToList();
More LINQ
在 GitHub 上提供。
或者,如果您出于某种原因不想使用外部 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());
不确定是否还有人在寻找其他方法来完成此操作。但是,我使用了以下代码根据匹配的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);
ToList()
上的<InputRow>
是多余的。你只需要用.ToList()
就可以了。 - Daniel Mann