C#中是否有类似于AddRange()的AddUnique方法用于alist?

39

我在C#中有一个列表:

       var list = new List<Car>();
       list.AddRange(GetGreenCars());
       list.AddRange(GetBigCars());
       list.AddRange(GetSmallCars());

问题是同一辆汽车在不同的函数中被返回,我不希望它们在列表中出现多次。每辆汽车都有一个唯一的名称属性。是否有办法让类似上面的代码仅在项目唯一时添加它们?

9个回答

64

一种选择是将它们相加并去除重复的部分:

var list = new List<Car>();
list.AddRange(GetGreenCars());
list.AddRange(GetBigCars());
list.AddRange(GetSmallCars());
list = list.Distinct().ToList();

我的集合中有重复的对象。 简单的 list = list.Distinct().ToList(); 不起作用。 - wotney
5
Distinct会删除重复的对象。如果您的对象实际上并不相同,您需要让它们实现Equals()和GetHashCode()方法。 - Ivo
这是使用简单列表(如整数列表)完成事情的最佳方式。 - shawty
最好的简单想法 - nazim hatipoglu

31

另一个选项是采取类似以下的做法:

public static void AddUnique<T>( this IList<T> self, IEnumerable<T> items )
{
    foreach(var item in items)
        if(!self.Contains(item))
            self.Add(item);
}


var list = new List<Car>();
list.AddUnique(GetGreenCars());
list.AddUnique(GetBigCars());
list.AddUnique(GetSmallCars());

1
这对我非常有帮助。 - Paul T. Rykiel

26

这里似乎不适合使用 List<T>,你可能想要一个 ISet<T> 实现,例如HashSet<T>(或者如果您需要排序的话,则使用SortedSet<T>)。

为了实现这一点,您需要编写一个 IEqualityComparer<T> 实现,该实现根据 Name 属性定义汽车之间的相等关系。如果这是“规范”的汽车相等性定义,您还可以考虑直接将此定义构建到 Car 类型本身中(object.Equalsobject.GetHashCode 并最好也实现 IEquatable<T>)。


14

如果您重写了Car.Equals()方法来确定一个汽车对象是否与另一个汽车对象相同,那么下面的代码应该可以正常工作,而无需编写扩展方法。

    var list = new List<Car>();
    list.AddRange(GetGreenCars()?.Except(list) ?? new List<Car>());
    list.AddRange(GetBigCars()?.Except(list) ?? new List<Car>());
    list.AddRange(GetSmallCars()?.Except(list) ?? new List<Car>());

1
不要忘记在Car类中重写Equals()和GetHashCode()方法,否则它将无法在Contains()方法中正确匹配对象。否则,这是最佳答案。 - Tsar Bomba
你也可以像这样比较对象内的特定ID:vm.SearchResults.AddRange(searchResultsOr.Where(listOr => !searchResultsAnd.Select(listAnd => listAnd.PostId).Contains(listOr.PostId))); - Yovav
5
可以进一步简化为:GetGreenCars().Except(list) - Edwin Stoteler

6

我创建了一个扩展方法,可以将只有唯一值的元素添加到实现了ICollection<T>(包括List<T>)的任何内容中,从IEnumerable<T>。与使用List<T>.Contains()的实现不同,这个方法允许您指定一个lambda表达式来确定两个项目是否相同。

/// <summary>
/// Adds only items that do not exist in source.  May be very slow for large collections and some types of source.
/// </summary>
/// <typeparam name="T">Type in the collection.</typeparam>
/// <param name="source">Source collection</param>
/// <param name="predicate">Predicate to determine whether a new item is already in source.</param>
/// <param name="items">New items.</param>
public static void AddUniqueBy<T>(this ICollection<T> source, Func<T, T, bool> predicate, IEnumerable<T> items)
{
    foreach (T item in items)
    {
        bool existsInSource = source.Where(s => predicate(s, item)).Any();
        if (!existsInSource) source.Add(item);
    }
}

使用方法:

source.AddUniqueBy<Foo>((s, i) => s.Id == i.Id, items);

你不需要将 Where 和 Any 进行串联。你可以直接将函数传递给 Any 方法。 - Jay

6

使用Linq的另一种选项:

public static void AddUnique<T>(this IList<T> self, IEnumerable<T> items)
{
  self.AddRange(
    items.Where(x => self.FirstOrDefault(y => y.Name == x.Name) ==
    null).ToList());
}

var list = new List<Car>();
list.AddUnique(GetGreenCars());
list.AddUnique(GetBigCars());
list.AddUnique(GetSmallCars());

0

无需使用 Linq 的选项! 如果程序员不能或不想使用 Linq,则有另一种选择。

var list = new List<Car>();
list.AddRange(GetGreenCars().FindAll((x) => !list.Contains(x)));
list.AddRange(GetBigCars().FindAll((x) => !list.Contains(x)));
list.AddRange(GetSmallCars().FindAll((x) => !list.Contains(x)));

如果初始列表为空,就像上面的例子一样,您实际上可以避免在第一个AddRange()中使用FindAll(...)。

这只在原始类型上可靠地工作。对于复合类型,您需要能够提供一个比较函数。 - Jay
@Jay 是的,可以通过在自定义类型上实现 IEquatable 来简单解决,所以没有问题。 - Novack

0

如果您想比较一个属性(例如 id),这应该可以工作。

var list = new List<string>();
list.AddRange(GetGreenCars().Where(greencar => !list.Contains(greencar, car => car.id == greencar.id)));
list.AddRange(GetBigCars().Where(bigcar => !list.Contains(bigcar, car => car.id == bigcar.id)));
list.AddRange(GetSmallCars().Where(smallcar => !list.Contains(smallcar, car => car.id == smallcar.id)));

0
假设您的 Get*Cars() 返回 Car 列表,另一个选项可能是:
var list = new List<Car>();
GetGreenCars().ForEach(c => { if (!list.Contains(c)) list.Add(c); });
GetBigCars().ForEach(c => { if (!list.Contains(c)) list.Add(c); });
GetSmallCars().ForEach(c => { if (!list.Contains(c)) list.Add(c); });

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