在C#中根据其中一个属性从列表中删除重复项

7

我有一个类类型的列表:

public class MyClass
{        
    public SomeOtherClass classObj;         
    public string BillId;           
}

public List<MyClass> myClassObject;

示例数值:

BillId = "123",classObj = {},
BillId = "999",classObj = {},
BillId = "777",classObj = {},
BillId = "123",classObj = {}

在上面的例子中,我们有BillId的重复值。我想要移除所有重复值(不是Distinct),所以结果只包含999777的值。
一种实现方式是:
  • 循环所有项
  • 获取唯一BillId的数量
  • 如果数量大于1,则将该BillId存储在另一个变量中
  • 再次循环并根据BillId删除项
有没有更直接的方法来实现这个呢?

3
你希望删除所有具有重复值的一对数据?在你的情况下,你想要删除两个123吗? - Joe Phillips
@JoePhillips 没错 - Shaggy
我编辑了标题,以区别于类似的问题 - Theodor Zoulias
6个回答

16

我认为这会起作用:

var result = myClassObject.GroupBy(x => x.BillId)
    .Where(x => x.Count() == 1)
    .Select(x => x.First());

在这里点此体验Fiddle。


是的,它返回一个布尔列表。 - IvanJazz
1
是的,在进行了快速测试后,我可以说这应该是被接受的答案。 - FireSarge

4

您也可以这样做,

var result = myClassObject.GroupBy(x => x.BillId)
              .Where(x => !x.Skip(1).Any())
              .Select(x => x.First());

FIDDLE


4
这是否也包括123 - jdmdevdotnet
1
我想要移除所有重复的数值(非唯一值),所以结果中只包含999和777。我认为他想要从列表中完全移除重复项。如果我理解有误,请纠正,但这将返回123。 - jdmdevdotnet
1
@Sajeetharan,你被踩的原因是你两个回答仍然是错误的。 - Joe Phillips
2
@Sajeetharan,此时您最好删除您的答案,除非您有不同于获得10多个赞的人的解决方案。 - Joe Phillips
1
现在你已经修复了错误,我认为你的答案是最好的,因为进行 !x.Skip(1).Any() 检查比 x.Count() == 1 更省力。 - Scott Chamberlain
显示剩余8条评论

1
这可能会有所帮助。
var result = myClassObject
          .GroupBy(x => x.BillId)
          .Where(x => x.Count()==1)
          .Select(x => x.FirstOrDefault());

1

.Where(x => x.Count()==1) 对我来说不太好用。

你可以尝试:

.GroupBy(x => x.codeLigne).Select(x => x.First()).ToList()

.Where(x => x.Count()==1) 对我来说不太好。” -- 那么您可能会对 DistinctBy 功能感兴趣。这个问题是关于完全删除重复项,而不保留任何一个的。您的答案在 这个问题 中也是有效的。 - Theodor Zoulias

0

试试这个。

var distinctList = myClassObject.GroupBy(m => m.BillId)
                                .Where(x => x.Count() == 1)
                                .SelectMany(x => x.ToList())
                                .ToList();

0
您要求一个简单的解决方案来解决问题,GroupBy+Where+Select 的解决方案完全满足这个要求,但您可能也对高性能和内存效率的解决方案感兴趣。下面是一种实现方式,使用了目前可用的所有工具(.NET 6+)以达到最大的效率:
/// <summary>
/// Returns a sequence of elements that appear exactly once in the source sequence,
/// according to a specified key selector function.
/// </summary>
public static IEnumerable<TSource> UniqueBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> comparer = default)
{
    ArgumentNullException.ThrowIfNull(source);
    ArgumentNullException.ThrowIfNull(keySelector);

    Dictionary<TKey, (TSource Item, bool Unique)> dictionary = new(comparer);
    if (source.TryGetNonEnumeratedCount(out int count))
        dictionary.EnsureCapacity(count); // Assume that most items are unique

    foreach (TSource item in source)
        CollectionsMarshal.GetValueRefOrAddDefault(dictionary, keySelector(item),
            out bool exists) = exists ? default : (item, true);

    foreach ((TSource item, bool unique) in dictionary.Values)
        if (unique)
            yield return item;
}

TryGetNonEnumeratedCount+EnsureCapacity组合在枚举源时可以对内存分配量产生显著影响,如果源是具有已知大小的类型,例如List<T>

CollectionsMarshal.GetValueRefOrAddDefault确保每个键只会被哈希一次,在键具有昂贵的GetHashCode实现的情况下,这可能会产生影响。

使用示例:

List<MyClass> unique = myClassObject.UniqueBy(x => x.BillId).ToList();

在线演示

上述UniqueBy与内置的DistinctBy LINQ运算符的区别在于,前者完全消除了重复项,而后者保留了每个重复元素的第一个实例。


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