【问题标题】:AddRange to a Collection将范围添加到集合
【发布时间】:2010-12-01 06:50:09
【问题描述】:

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

当然,带有属性的 foreach。添加将起作用。但是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 中没有找到类似的东西。

糟糕的设计?只需调用添加?缺少明显的东西?

【问题讨论】:

  • 请记住,来自 LINQ 的 Q 是“查询”,实际上是关于数据检索、投影、转换等。修改现有集合确实不属于 LINQ 的预期目的范围,这就是为什么LINQ 没有为此提供任何现成的东西。但是扩展方法(尤其是您的示例)将是理想的选择。
  • 一个问题,ICollection&lt;T&gt; 好像没有Add 方法。 msdn.microsoft.com/en-us/library/… 不过Collection&lt;T&gt; 有一个。
  • @TimGoodman - 这是非通用接口。见msdn.microsoft.com/en-us/library/92t2ye13.aspx
  • “修改现有集合确实不属于 LINQ 的预期目的范围”。 @Levi 那么为什么首先要有Add(T item) 呢?提供添加单个项目的能力,然后期望所有调用者迭代以便一次添加多个项目,这似乎是一种半生不熟的方法。你的说法对IEnumerable&lt;T&gt; 肯定是正确的,但我发现自己对ICollections 感到沮丧不止一次。我不反对你,只是发泄一下。

标签: c# collections c#-3.0 extension-methods


【解决方案1】:

.NET4.5 开始,如果你想要单线,你can use System.Collections.Generic ForEach.

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

甚至更短

source.ForEach(destination.Add);

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

另外不要尝试像这样分配它

var x = source.ForEach(destination.Add) 

因为ForEach 是无效的。

编辑:复制自 cmets,Lippert's opinion on ForEach

【讨论】:

【解决方案2】:

同意上面的一些人和 Lipert 的观点。 就我而言,经常这样做:

ICollection<int> A;
var B = new List<int> {1,2,3,4,5};
B.ForEach(A.Add);

在我看来,这种操作的扩展方法有点多余。

【讨论】:

  • 您的答案没有添加许多旧答案尚未建议的任何内容。
  • 当我想要这个时我能做什么:aList = If(True,Nothing,aList.AddRange(...)) 在表达式 B 我想添加项目。但 If 表达式不允许这样做。表达式没有任何价值...
  • 可能我没有完全理解你的问题,但是如果要按条件添加项目,可以这样:ICollection&lt;int&gt; A = new List&lt;int&gt;(); var B = new List&lt;int&gt; { 1, 2, 3, 4, 5 }; B.ForEach(s =&gt;{ if (s &gt; 3) A.Add(s); });
【解决方案3】:

不,这似乎完全合理。有一个List&lt;T&gt;.AddRange() 方法基本上就是这样做的,但要求您的集合是具体的List&lt;T&gt;

【讨论】:

  • 谢谢;非常正确,但大多数公共属性都遵循 MS 指南,而不是列表。
  • 是的 - 我给出的更多理由是为什么我认为这样做没有问题。只要意识到它会比 List 版本效率低(因为 list 可以预先分配)
  • 请注意,如果使用不当,.NET Core 2.2 中的 AddRange 方法可能会出现奇怪的行为,如本期所示:github.com/dotnet/core/issues/2667
【解决方案4】:

这是一个更高级/生产就绪的版本:

    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's answer 可能看起来很幼稚,太简单了,但它适用于异构列表。是否可以让这段代码支持这个用例?
  • 例如:destination 是一个抽象类 Shape 的列表。 sourceCircle 的列表,一个继承的类。
【解决方案5】:

或者你可以像这样制作一个 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);

【讨论】:

    【解决方案6】:

    在运行循环之前尝试在扩展方法中转换为 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);
            }
        }
    }
    

    【讨论】:

    • as 运算符永远不会抛出。如果destination 不能被强制转换,list 将为空并且else 块将被执行。
    • 啊!交换条件分支,为了所有神圣的爱!
    • 其实我是认真的。主要原因是额外的认知负荷,这通常非常困难。您一直在尝试评估负面条件,这通常相对困难,无论如何您都有两个分支,(IMO)更容易说“如果 null”这样做,“否则”这样做,而不是相反。这也是关于默认值的,它们应该尽可能多地是积极的概念,.eg `if (!thing.IsDisabled) {} else {}' 要求你停下来思考'啊,不是禁用意味着启用,对,明白了,所以另一个分支是当它被禁用时)。难以解析。
    • 解释“something != null”并不比解释“something == null”更难。然而,否定运算符是完全不同的东西,在您的最后一个示例中,重写 if-else-statement 将 elliminate 该运算符。这在客观上是一种改进,但与原始问题无关。在那种特殊情况下,这两种形式是个人喜好的问题,鉴于上述推理,我更喜欢“!=”-操作符。
    • 模式匹配会让大家开心... ;-) if (destination is List&lt;T&gt; list)
    【解决方案7】:

    您可以将 IEnumerable 范围添加到列表中,然后将 ICollection = 设置到列表中。

            IEnumerable<T> source;
    
            List<item> list = new List<item>();
            list.AddRange(source);
    
            ICollection<item> destination = list;
    

    【讨论】:

    【解决方案8】:

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

    【讨论】:

    • 此外,每次添加都会有一个集合更改通知,而不是使用 AddRange 的一个批量通知。
    【解决方案9】:

    C5 Generic Collections Library 类都支持AddRange 方法。 C5 有一个更健壮的接口,它实际上公开了其底层实现的所有特性,并且与System.Collections.GenericICollectionIList 接口兼容,这意味着C5 的集合可以很容易地替换为底层实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-07-27
      • 1970-01-01
      • 2022-08-04
      • 1970-01-01
      • 1970-01-01
      • 2022-11-03
      • 1970-01-01
      • 2020-09-29
      相关资源
      最近更新 更多