如何将项目添加到IEnumerable <T>集合?


405

我的问题如上面的标题所示。例如,

IEnumerable<T> items = new T[]{new T("msg")};
items.ToList().Add(new T("msg2"));

但毕竟里面只有1个物品

我们可以采用类似的方法items.Add(item)吗?

List<T>


4
IEnumerable<T>仅用于查询集合。它是LINQ框架的骨干。它始终是一些其他的集合,如的抽象Collection<T>List<T>Array。该接口仅提供一种GetEnumerator方法,该方法返回一个实例,IEnumerator<T>该实例一次使扩展集合的遍历成为可能。LINQ扩展方法受此接口限制。IEnumerable<T>被设计为只读,因为它可能表示多个集合的各个部分的集合。
约旦

3
对于此示例,只需声明IList<T>而不是IEnumerable<T>IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888 '17

Answers:


480

您不能,因为IEnumerable<T>不一定代表可以添加项目的集合。实际上,它不一定完全代表一个集合!例如:

IEnumerable<string> ReadLines()
{
     string s;
     do
     {
          s = Console.ReadLine();
          yield return s;
     } while (!string.IsNullOrEmpty(s));
}

IEnumerable<string> lines = ReadLines();
lines.Add("foo") // so what is this supposed to do??

但是,您可以做的是创建一个 IEnumerable对象(未指定类型),该对象在枚举时将提供旧对象的所有项目,再加上您自己的一些项目。您Enumerable.Concat为此使用:

 items = items.Concat(new[] { "foo" });

不会更改数组对象(无论如何您都不能将项目插入到数组中)。但是它将创建一个新对象,该对象将列出阵列中的所有项目,然后列出“ Foo”。此外,该新对象将跟踪数组中的更改(即,只要枚举它,您将看到项的当前值)。


3
创建一个数组以添加单个值的开销有点高。为单个值Concat定义扩展方法更具可重用性。
JaredPar

4
这取决于-可能值得,但是我很想将其Concat称为过早优化,除非在循环中重复调用;如果是这样,无论如何您都会遇到更大的问题,因为每次您Concat都会获得一层以上的枚举数-因此,到最后,对的单次调用MoveNext将导致一系列调用的长度等于迭代次数。
帕维尔米纳夫

2
@帕维尔,嘿。通常,困扰我的不是创建数组的内存开销。这是我觉得很烦的额外输入内容:)。
JaredPar

2
所以我了解了IEnumerable的功能。应该仅查看而不打算对其进行修改。谢谢你们
ldsenow

7
我对IEnumerable<T>C#中的语法糖有一个Connect功能请求,包括重载operator+Concat(顺便说一句,您知道吗,在VB中,您可以IEnumerable像建立数组一样进行索引-它ElementAt后台使用):connect.microsoft.com / VisualStudio / feedback /…。如果我们Concat在左侧和右侧也都获得了两个以上的重载,那么这将减少键入内容的范围xs +=x -因此请戳Mads(Torgersen)在C#5.0中优先处理:)
Pavel Minaev

98

该类型IEnumerable<T>不支持此类操作。该IEnumerable<T>界面的目的是允许消费者查看集合的内容。不修改值。

当您执行诸如.ToList()。Add()之类的操作时,您将创建一个新List<T>值并将一个值添加到该列表。它与原始列表没有连接。

您可以使用添加扩展方法创建IEnumerable<T>具有附加值的新商品。

items = items.Add("msg2");

即使在这种情况下,它也不会修改原始IEnumerable<T>对象。可以通过持有对它的引用来验证。例如

var items = new string[]{"foo"};
var temp = items;
items = items.Add("bar");

在这组操作之后,变量temp将仍然仅引用值集中的单个元素“ foo”的可枚举,而项将引用值“ foo”和“ bar”的不同的枚举。

编辑

我一直忘记,Add不是典型的扩展方法,IEnumerable<T>因为它是我最终定义的第一个方法。这里是

public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value) {
  foreach ( var cur in e) {
    yield return cur;
  }
  yield return value;
}

12
那叫做Concat:)
Pavel Minaev

3
@Pavel,感谢您指出这一点。但是,即使Concat也无法正常工作,因为它需要另一个IEnumerable <T>。我粘贴了在项目中定义的典型扩展方法。
JaredPar

10
我不会这样称呼Add,因为Add实际上在任何其他.NET类型(不仅是集合)上,都将就地改变集合。也许With吧?甚至可能只是的另一重载Concat
帕维尔米纳夫

4
我同意Concat重载可能是一个更好的选择,因为Add通常意味着可变性。
Dan Abramov

15
修改身体的方式return e.Concat(Enumerable.Repeat(value,1));呢?
罗伊·廷克

58

您是否考虑过使用ICollection<T>IList<T>接口,它们之所以存在是因为您想在上使用Add方法IEnumerable<T>

IEnumerable<T>用于将类型“标记”为……好,可枚举或只是一系列项目,而不必一定保证真正的基础对象是否支持添加/删除项目。还要记住,这些接口是实现的,IEnumerable<T>因此您也将获得所有扩展方法IEnumerable<T>


31

一对夫妇短,甜美的扩展方法上IEnumerable,并IEnumerable<T>为我做:

public static IEnumerable Append(this IEnumerable first, params object[] second)
{
    return first.OfType<object>().Concat(second);
}
public static IEnumerable<T> Append<T>(this IEnumerable<T> first, params T[] second)
{
    return first.Concat(second);
}   
public static IEnumerable Prepend(this IEnumerable first, params object[] second)
{
    return second.Concat(first.OfType<object>());
}
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> first, params T[] second)
{
    return second.Concat(first);
}

优雅(很好,除了非通用版本)。不幸的是,这些方法不在BCL中。


第一个Prepend方法给我一个错误。“'object []'不包含'Concat'的定义,最佳扩展方法重载'System.Linq.Enumerable.Concat <TSource>(System.Collections.Generic.IEnumerable <TSource>,System.Collections.Generic。 IEnumerable <TSource>)'有一些无效的参数”
Tim Newton

@TimNewton,您做错了。谁知道原因,没人能从那段错误代码中看出来。Protip:始终捕获异常并ToString()对其进行处理,因为这会捕获更多错误详细信息。我猜您不在include System.Linq;文件的开头,但是谁知道呢?尝试自己搞清楚,创建一个最小的原型,如果不能的话,该原型会显示相同的错误,然后在问题中寻求帮助。

我只是复制并粘贴了上面的代码。除了Prepend给出了我提到的错误之外,所有其他方法都可以正常工作。它不是运行时异常,而是编译时异常。
蒂姆·牛顿

@TimNewton:我不知道您在说什么能完美地工作,显然您是错误地忽略了编辑中的更改,现在我必须走了,先生,好。

也许与我们的环境有所不同。我正在使用VS2012和.NET 4.5。不要再浪费时间了,我只是想指出上面的代码有问题,尽管很小。无论如何,我都认为这个答案很有用。谢谢。
蒂姆·牛顿


22

否IEnumerable不支持向其中添加项目。

您的“替代”是:

var List = new List(items);
List.Add(otherItem);

4
您的建议不是唯一的选择。例如,这里的ICollection和IList都是合理的选择。
杰森

6
但是您也不能实例化。
Paul van Brenk 09年

16

要添加第二条消息,您需要-

IEnumerable<T> items = new T[]{new T("msg")};
items = items.Concat(new[] {new T("msg2")})

1
但是您还需要将Concat方法的结果分配给items对象。
Tomasz Przychodzki

1
是的,Tomasz你是对的。我修改了最后一个表达式,以将串联值分配给项目。
Aamol

8

您不仅不能像状态那样添加项目,而且如果将项目添加到List<T>具有现有枚举器的(或几乎所有其他非只读集合)中,则枚举器将无效(InvalidOperationException此后抛出)。

如果要汇总某种数据查询的结果,则可以使用Concat扩展方法:

编辑:我最初Union在示例中使用扩展名,这是不正确的。我的应用程序广泛使用它来确保重叠的查询不会重复结果。

IEnumerable<T> itemsA = ...;
IEnumerable<T> itemsB = ...;
IEnumerable<T> itemsC = ...;
return itemsA.Concat(itemsB).Concat(itemsC);

8

我只是在这里说,除了Enumerable.Concat扩展方法外,Enumerable.Append.NET Core 1.1.1中似乎还有另一种方法。后者允许您将单个项目连接到现有序列。所以Aamol的答案也可以写成

IEnumerable<T> items = new T[]{new T("msg")};
items = items.Append(new T("msg2"));

不过,请注意,此函数不会更改输入序列,它只是返回将给定序列和附加项放在一起的包装器。


4

其他人已经对为什么您不能(也应该不!)向项添加内容做出了很好的解释IEnumerable。我只会补充说,如果您希望继续编码到表示一个集合的接口并想要添加方法,则应该将代码编码为ICollectionIList。作为额外的好处,这些接口实现了IEnumerable


1

你可以这样做。

//Create IEnumerable    
IEnumerable<T> items = new T[]{new T("msg")};

//Convert to list.
List<T> list = items.ToList();

//Add new item to list.
list.add(new T("msg2"));

//Cast list to IEnumerable
items = (IEnumerable<T>)items;

8
5年前,该问题以更完整的方式回答。将接受的答案视为对问题的良好答案的示例。
pquest

1
最后一行是:items =(IEnumerable <T>)list;
贝伦·马丁'18

0

最简单的方法就是

IEnumerable<T> items = new T[]{new T("msg")};
List<string> itemsList = new List<string>();
itemsList.AddRange(items.Select(y => y.ToString()));
itemsList.Add("msg2");

然后,您还可以将列表作为IEnumerable返回,因为它实现了IEnumerable接口



-8

当然可以(我不考虑您的T生意):

public IEnumerable<string> tryAdd(IEnumerable<string> items)
{
    List<string> list = items.ToList();
    string obj = "";
    list.Add(obj);

    return list.Select(i => i);
}
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.