将范围添加到集合


109

一位同事今天问我如何将范围添加到集合中。他有一个继承自的类Collection<T>。有一个该类型的get-only属性,其中已经包含一些项目。他想将另一个集合中的项目添加到属性集合中。他如何以C#3友好的方式这样做?(请注意有关“仅获取”属性的约束,该约束会阻止执行Union和重新分配之类的解决方案。)

当然,与财产有关。添加将起作用。但是,List<T>风格的AddRange会更加优雅。

编写扩展方法很容易:

public static class CollectionHelpers
{
    public static void AddRange<T>(this ICollection<T> destination,
                                   IEnumerable<T> source)
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}

但是我有种自己在重新发明轮子的感觉。我没有发现任何类似的System.Linqmorelinq

设计不好?只需致电添加?缺少明显之处?


5
请记住,来自LINQ的Q是“查询”,实际上是关于数据检索,投影,转换等。修改现有集合确实不属于LINQ预期用途的领域,因此LINQ不会提供任何超出范围的内容,现成的。但是扩展方法(尤其是您的示例)将是理想的选择。
李维

一个问题,ICollection<T>似乎没有Add方法。msdn.microsoft.com/en-us/library/… 但是Collection<T>有一个。
蒂姆·古德曼

@TimGoodman-这是非通用接口。请参阅msdn.microsoft.com/en-us/library/92t2ye13.aspx
TrueWill 2013年

“修改现有的收藏确实不属于LINQ的预期范围”。@Levi那为什么还要Add(T item)放在首位呢?似乎是一种半生半熟的方法,它提供了添加单个项目的功能,然后期望所有调用者进行迭代以一次添加多个。您的声明当然是正确的,IEnumerable<T>但我发现自己ICollections不止一次感到沮丧。我不同意你,只是发泄。
akousmata

Answers:


62

不,这似乎完全合理。有一个List<T>.AddRange()方法基本上可以做到这一点,但是要求您的集合是具体的List<T>


1
谢谢; 非常正确,但是大多数公共财产都遵循MS准则,而不是列表。
TrueWill

7
是的-我提供的更多理由是为什么我认为这样做没有问题。只是意识到它会比List <T>版本效率低(因为list <T>可以预分配)
Reed Copsey

请注意,如果使用不正确,.NET Core 2.2中的AddRange方法可能会显示奇怪的行为,如本期所示:github.com/dotnet/core/issues/2667
Bruno,

36

运行循环之前,请尝试使用扩展方法转换为List。这样,您就可以利用List.AddRange的性能。

public static void AddRange<T>(this ICollection<T> destination,
                               IEnumerable<T> source)
{
    List<T> list = destination as List<T>;

    if (list != null)
    {
        list.AddRange(source);
    }
    else
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}

2
as运营商将不会抛出。如果destination无法强制转换,list则为null,然后else执行该块。
rymdsmurf 2014年

4
啊!交换条件分支,换取对所有圣物的热爱!
nicodemus13

13
我是认真的,实际上,主要原因是额外的认知负担,这通常确实很困难。您一直在尝试评估负面条件,这通常相对来说比较困难,无论如何,您都有两个分支,说(if null)这样做,(else)这样做(而不是相反)更容易(IMO)。它还与默认值有关,它们应该尽可能多地成为积极的概念,例如.if(!thing.IsDisabled){} else {}”要求您停止思考,“啊,不是禁用意味着启用,对,知道了,所以另一个分支就是它被禁用时)。很难解析。
nicodemus13年

13
解释“ something!= null”并不比解释“ something == null”困难。否定运算符是另一回事,在最后一个示例中,重写if-else语句将消除该运算符。从客观上来说,这是一种改进,但与原始问题无关。在这种特殊情况下,这两种形式都是个人喜好问题,考虑到上述原因,我希望使用“!=”-运算符。
rymdsmurf '16

15
模式匹配将使每个人都高兴... ;-)if (destination is List<T> list)
雅各布·弗谢

28

因为.NET4.5如果您想要单线,则可以使用System.Collections.GenericForEach。

source.ForEach(o => destination.Add(o));

甚至更短为

source.ForEach(destination.Add);

在性能方面,它与每个循环(语法糖)相同。

不要尝试像

var x = source.ForEach(destination.Add) 

原因ForEach是无效的。

编辑:从评论中复制,利伯特对ForEach的看法



1
应该是source.ForEach(destination.Add)吗?
弗兰克(Frank)

4
ForEach似乎只在定义List<T>,不是Collection吗?
保护者


更新了Eric Lippert的博客文章的链接:编码中的精彩历程| “ foreach” vs“ ForEach”
亚历山大

19

请记住,每个组件Add都会检查集合的容量,并在必要时调整大小(较慢)。使用AddRange,将设置集合的容量,然后添加项目(更快)。此扩展方法将非常慢,但可以使用。


3
除此之外,每次添加都会有一个集合更改通知,与一个带有AddRange的批量通知相反。
尼克·乌德尔2014年

3

这是更高级/可用于生产的版本:

    public static class CollectionExtensions
    {
        public static TCol AddRange<TCol, TItem>(this TCol destination, IEnumerable<TItem> source)
            where TCol : ICollection<TItem>
        {
            if(destination == null) throw new ArgumentNullException(nameof(destination));
            if(source == null) throw new ArgumentNullException(nameof(source));

            // don't cast to IList to prevent recursion
            if (destination is List<TItem> list)
            {
                list.AddRange(source);
                return destination;
            }

            foreach (var item in source)
            {
                destination.Add(item);
            }

            return destination;
        }
    }

rymdsmurf的答案看起来很幼稚,太简单了,但它适用于异构列表。是否可以使此代码支持此用例?
richardsonwtr

例如:destinationShape抽象类的列表。sourceCircle继承的类的列表。
richardsonwtr



0

或者,您可以像这样进行ICollection扩展:

 public static ICollection<T> AddRange<T>(this ICollection<T> @this, IEnumerable<T> items)
    {
        foreach(var item in items)
        {
            @this.Add(item);
        }

        return @this;
    }

使用它就像在列表上使用它一样:

collectionA.AddRange(IEnumerable<object> items);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.