AddRange to a Collection
今天一位同事问我如何为收藏添加范围。他有一个从以东继承的阶级。该类型的get-only属性已包含某些项。他想将另一个集合中的项添加到属性集合中。他怎么能以一种友好的方式做到这一点?(注意get only属性的约束,这会阻止执行联合和重新分配等解决方案。)
当然,一个有属性的foreach。Add会起作用。但是,一个List样式的addrange将要优雅得多。
编写扩展方法非常简单:
1 2 3 4 5 6 7 8 9 10 11
| 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.Linq或morelinq没有发现类似的东西。
糟糕的设计?只需调用ADD?错过了明显的?
- 记住,来自LINQ的Q是"查询",实际上是关于数据检索、投影、转换等。修改现有集合实际上并不属于LINQ的预期目的范围,这就是为什么LINQ没有为此提供任何现成的功能。但是扩展方法(尤其是您的示例)是实现这一点的理想方法。
- 一个问题是,ICollection似乎没有Add方法。msdn.microsoft.com/en-us/library/&hellip;但是Collection有一个。
- @Timgoodman——这是非通用接口。请参阅msdn.microsoft.com/en-us/library/92t2ye13.aspx
- "修改现有集合并不属于Linq的预期目的"。@利未那为什么一开始就有以东十一〔三〕呢?似乎是一种半生不熟的方法,它提供了添加单个项的能力,然后期望所有调用方都进行迭代,以便一次添加多个项。你的说法对IEnumerable来说当然是正确的,但我发现我不止一次对ICollections感到失望。我不反对你,只是发泄一下。
不,这似乎很合理。有一个List.addrange()方法基本上就是这样做的,但需要您的集合是一个具体的List。
- 谢谢,非常正确,但是大多数公共财产都遵循微软的指导方针,而不是清单。
- 是的-我给了它更多的理由来解释为什么我认为这样做没有问题。只需意识到它比list版本效率低(因为list可以预先分配)
在运行循环之前,尝试强制转换为在扩展方法中列出。这样就可以利用list.addrange的性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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);
}
}
} |
- 这可能是一个有点不切实际的问题,但是当destination集合不能强制转换为List集合时会发生什么?list是自动变为null还是抛出异常?
- as操作符永远不会抛出。如果无法强制转换destination,list将为空,执行else块。
- 阿格尔!交换条件分支,为所有神圣的爱!
- @尼哥底缪斯13,(假设你至少有点认真)你换它们的理由是什么?首先考虑一下这件事,这是我们进行检查的唯一原因,难道不更自然吗?
- 事实上,我是认真的。主要原因是它是额外的认知负荷,这通常非常困难。你一直在尝试评估消极的情况,这通常是相对困难的,你有两个分支,不管怎样,这是(imo)更容易说'如果空'做这个,'否则'做这个,而不是相反。它还涉及到默认值,它们应该是尽可能频繁的积极概念,例如"if(!Thing.is disabled)else要求您停止并思考"啊,不被禁用意味着启用了,对,得到了,所以另一个分支是在禁用时)。难以解析。
- 解释"某物!=null"并不比解释"something==null"更困难。但是否定运算符是完全不同的,在上一个示例中,重写if-else语句将省略该运算符。这是一个客观的改进,但与原始问题无关。在这种情况下,这两种形式都是个人喜好的问题,我更喜欢"!="-运算符,给出上述推理。
- 模式匹配会让每个人都高兴…;-)if (destination is List list)。
因为.NET4.5如果你想要一个衬里,你可以用System.Collections.Generic作前臂。
1
| source.ForEach(o => destination.Add(o)); |
甚至更短
1
| source.ForEach(destination.Add); |
从性能上讲,它与每个循环(句法糖)相同。
也不要尝试像这样分配它
1
| var x = source.ForEach(destination.Add) |
因为ForEach无效。
- 就我个人而言,我和Lippert在这一个:blogs.msdn.com/b/ericlippet/archive/2009/05/18/&hellip;
- 它应该是source.foreach(destination.add)吗?
- @弗兰克,你好,弗兰克,固定谢谢注意:)
- ForEach似乎只在List上定义,而不是在Collection上定义?
- 现在可以在web.archive.org/web/20190316010649/https://hellip;上找到lippert。
记住,每个Add都将检查集合的容量,并在必要时调整其大小(较慢)。使用AddRange时,将设置收集容量,然后添加项目(更快)。这种扩展方法将非常缓慢,但会起作用。
- 要添加到其中,每个添加都将有一个集合更改通知,而不是一个带有addrange的批量通知。
c5通用集合库类都支持AddRange方法。c5有一个更加健壮的接口,它实际上公开了其底层实现的所有特性,并且与System.Collections.GenericICollection和IList接口兼容,这意味着C5的集合可以很容易地替换为底层实现。
或者您可以这样做一个ICollection扩展:
1 2 3 4 5 6 7 8 9
| public static ICollection<T> AddRange<T>(this ICollection<T> @this, IEnumerable<T> items)
{
foreach(var item in items)
{
@this.Add(item);
}
return @this;
} |
使用它就像在列表上使用它一样:
1
| collectionA.AddRange(IEnumerable<object> items); |
可以将IEnumerable范围添加到列表中,然后将ICollection=设置为列表。
1 2 3 4 5 6
| IEnumerable <T > source ;
List <item > list = new List <item >();
list .AddRange(source );
ICollection <item > destination = list ; |
- 虽然这在功能上有效,但它打破了Microsoft将集合属性设置为只读的指导原则(msdn.microsoft.com/en-us/library/ms182327.aspx)