如何向IList添加一系列项目?

102

IList<T>没有AddRange()方法。

如何在不通过迭代项并使用Add()方法的情况下向IList<T>添加一系列项目?

6个回答

98
如果您查看List<T>的C#源代码,我认为List<T>.AddRange()有一些简单循环无法解决的优化。因此,扩展方法只需检查IList<T>是否为List<T>,如果是,则使用其本地AddRange()。
在源代码中浏览时,您会看到.NET人员在自己的LINQ扩展中做类似的事情,例如.ToList()(如果它是列表,则转换它...否则创建它)。
public static class IListExtension
{
    public static void AddRange<T>(this IList<T> list, IEnumerable<T> items)
    {
        if (list == null) throw new ArgumentNullException(nameof(list));
        if (items == null) throw new ArgumentNullException(nameof(items));

        if (list is List<T> asList)
        {
            asList.AddRange(items);
        }
        else
        {
            foreach (var item in items)
            {
                list.Add(item);
            }
        }
    }
}

6
从优化的角度来看,你在这里实际上将list转换为List<T>两次。其中一次可以使用as关键字进行优化。 - bashis
好的建议 @bashis。我总是在双重转换的成本和GC清理我们新变量之间犹豫不决。但如果我们确实可以这样做,那么我们可以将var listCasted = list as List<T>; if(listCasted != null)...也许c# 6声明表达式会改变这种模式:if(myVar.As(out myVarCasted)) myVarCasted...) - BlackjacketMack
你能否根据提到的优化更新代码?我对最新的功能不是很熟悉。@BlackjacketMack - user4344677
2
@zuckerthoben - 我刚刚运行了一个测试,使用两种方法迭代了一百万次,性能没有任何差异。所以我不会称其为优化...此外,它增加了一行代码(但减少了括号转换)。无论如何,我现在可能会使用'as':var listCasted = list as List<T>;如果(listCasted != null){listCasted.AddRange(items);}。我认为这并不值得更新答案,但是介绍作为一种语法替代方案还是很好的。 - BlackjacketMack
3
目前你可以这样做:if (list is List<T> castedList) { castedList.AddRange(items); }。意思是,如果listList<T>类型的,就将它转换为castedList,然后调用AddRange方法添加元素items到列表中。 - André Mantas

75

AddRange 是定义在 List<T> 上的,而不是接口上。

您可以将变量声明为 List<T> 而不是 IList<T>,或者将其转换为 List<T>,以便访问 AddRange

((List<myType>)myIList).AddRange(anotherList);

这种做法不够好(请参见下面的评论),因为一个 IList<T> 可能不是一个 List<T>,而是实现了该接口的其他类型,并且很可能没有 AddRange 方法 - 在这种情况下,只有当您的代码在运行时抛出异常时才会发现。

因此,除非您确信该类型确实是 List<T>,否则不应尝试使用 AddRange

一种方法是使用 isas 运算符进行类型检测(自 C# 7 起)。

if(myIList is List<T>)
{
   // can cast and AddRange
}
else
{
   // iterate with Add
}

2
如果类型是生成的,则不希望更改生成的代码(因为它可能会被覆盖)。可以强制转换或使用LINQ Concat,如@Self_Taught_Programmer 回答 - Oded
2
@mohsen.d - 如果这是你的代码,最好将类型声明为List<T>(或者,如果这对你来说不是一个好选择,在需要AddRange时进行转换以保持局部性 - 这是一项非常低成本的操作)。 - Oded
58
不,不,不。之所以一开始它是一个IList<T>,是因为它可能不是List<T>的实现。如果你真的需要AddRange方法,请像BlackjacketMack所示编写扩展方法。 - Derek Greer
6
不知道为什么这篇文章会有那么多赞,因为如果用于除了List<T>(例如数组)之外的任何东西上面,明显会抛出InvalidCastException异常。 - bashis
3
但是,使用强制类型转换并不是一个好主意。它可能导致性能开销。 - Gul Ershad
显示剩余6条评论

27
你可以像这样做:

你可以这样做:

IList<string> oIList1 = new List<string>{"1","2","3"};
IList<string> oIList2 = new List<string>{"4","5","6"};
IList<string> oIList3 = oIList1.Concat(oIList2).ToList();

基本上,您可以使用Concat()扩展和ToList()来获得与AddRange()类似的功能。

Source


2
你的方法存在问题,因为Enumerable.Concat是由System.Linq.Enumerable实现的,该方法的返回值是IEnumerable<TSource>。因此,我认为它不应该转换回IList<TSource> - 由于我们没有检查源代码,它可能会返回其他内容。尽管如此,也不能保证它不会发生变化 - 因此,在支持多个.NET版本时必须特别注意。 - jweyrich

10

您也可以编写类似于以下内容的扩展方法:

internal static class EnumerableHelpers
{
    public static void AddRange<T>(this IList<T> collection, IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            collection.Add(item);
        }
    }
}

用法:

IList<int> collection = new MyCustomList(); //Or any other IList except for a fixed-size collection like an array
var items = new[] {1, 4, 5, 6, 7};
collection.AddRange(items);

它仍然在迭代项,但您不必每次调用时编写迭代器或强制转换。


4

如果你要添加的是 List<T> 或者你能够调用 ToList() ,则可以使用 LINQ 的另一种答案:

IEnumerable<string> toAdd = new string[] {"a", "b", "c"};
IList<string> target = new List<string>();

toAdd.ToList().ForEach(target.Add);

1

IList没有AddRange()方法,但是有Concat()方法可以合并你的集合。


1
最好添加一些代码示例,以使您的答案更有用。 - AndrewSilver
1
IList<T>.Concat(IEnumerable<T>) 返回连接后的序列作为可枚举对象,因此不会改变列表。这可能是期望的结果,也可能不是。 - Manfred

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