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

Posted

技术标签:

【中文标题】如何将项目添加到 IEnumerable<T> 集合?【英文标题】:How can I add an item to a IEnumerable<T> collection? 【发布时间】:2010-11-15 16:01:52 【问题描述】:

我的问题如上面的标题。例如

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

但毕竟里面只有 1 个项目。我们可以有像items.Add(item) 这样的方法吗,比如List&lt;T&gt;

【问题讨论】:

IEnumerable&lt;T&gt; 仅用于查询集合。它是 LINQ 框架的支柱。它始终是一些其他集合的抽象,例如 Collection&lt;T&gt;List&lt;T&gt;Array。该接口只提供了一个GetEnumerator 方法,该方法返回一个IEnumerator&lt;T&gt; 的实例,该实例允许一次遍历一个扩展集合。 LINQ 扩展方法受此接口限制。 IEnumerable&lt;T&gt; 被设计为只读,因为它可能代表多个集合的一部分的聚合。 对于这个例子,只声明IList&lt;T&gt;而不是IEnumerable&lt;T&gt;IList&lt;T&gt; items = new T[]new T("msg"); items.Add(new T("msg2")); 【参考方案1】:

您不能,因为IEnumerable&lt;T&gt; 不一定代表可以添加项目的集合。事实上,它根本不一定代表一个集合!例如:

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??

然而,你可以做的是创建一个 new IEnumerable 对象(未指定类型),在枚举时,它将提供旧对象的所有项目,以及你自己的一些.您为此使用Enumerable.Concat

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

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

【讨论】:

创建一个数组来添加单个值的开销有点高。为单个值 Concat 定义扩展方法更可重用。 这取决于 - 这可能是值得的,但我很想将其称为过早优化,除非在循环中反复调用 Concat;如果是这样,无论如何你都会遇到更大的问题,因为每次Concat,你都会多得到一层枚举器 - 所以到最后,对MoveNext的单次调用将导致一连串长度相等的调用到迭代次数。 @Pavel,呵呵。通常,困扰我的不是创建数组的内存开销。这是我觉得烦人的额外打字:)。 所以我更了解 IEnumerable 的作用。它应该只是查看而不打算修改。谢谢各位 我有一个 Connect 功能请求,要求在 C# 中为 IEnumerable&lt;T&gt; 提供语法糖,包括将 operator+ 重载为 Concat(顺便说一下,你知道在 VB 中,你可以索引 @987654333 @ 好像它是一个数组 - 它在引擎盖下使用 ElementAt):connect.microsoft.com/VisualStudio/feedback/…。如果我们还得到两个Concat 的重载以在左侧和右侧都取一个项目,这将减少输入到xs +=x - 所以去戳Mads (Torgersen) 在C# 5.0 中优先考虑这个: )【参考方案2】:

IEnumerable&lt;T&gt; 类型不支持此类操作。 IEnumerable&lt;T&gt; 接口的目的是允许消费者查看集合的内容。不要修改值。

当您执行 .ToList().Add() 之类的操作时,您正在创建一个新的 List&lt;T&gt; 并向该列表添加一个值。它与原始列表无关。

您可以做的是使用 Add 扩展方法创建一个带有附加值的新 IEnumerable&lt;T&gt;

items = items.Add("msg2");

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

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

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

编辑

我经常忘记 Add 不是 IEnumerable&lt;T&gt; 上的典型扩展方法,因为它是我最终定义的第一个方法。在这里

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

【讨论】:

@Pavel,感谢您指出这一点。即使 Concat 也无法工作,因为它需要另一个 IEnumerable。我粘贴了我在项目中定义的典型扩展方法。 虽然我不会称它为Add,因为Add 在几乎任何其他.NET 类型(不仅仅是集合)上都会就地改变集合。也许With?或者它甚至可能只是Concat 的另一个重载。 我同意Concat 重载可能是更好的选择,因为Add 通常意味着可变性。 将body修改为return e.Concat(Enumerable.Repeat(value,1));怎么样? 将其命名为Add 的问题在于List.Add 在改变原始列表时不会返回List,而您的扩展方法IEnumerable.Add 返回IEnumerable,但是不会改变原来的。我认为 Add 是一个不可取的名字。【参考方案3】:

您是否考虑过使用ICollection&lt;T&gt;IList&lt;T&gt; 接口,它们的存在正是因为您希望在IEnumerable&lt;T&gt; 上使用Add 方法。

IEnumerable&lt;T&gt; 用于将类型“标记”为......嗯,可枚举或只是一系列项目,而不必保证真正的底层对象是否支持添加/删除项目。还要记住,这些接口实现了IEnumerable&lt;T&gt;,因此您也可以获得使用IEnumerable&lt;T&gt; 获得的所有扩展方法。

【讨论】:

【参考方案4】:

在 .net Core 中,有一个方法 Enumerable.Append 可以做到这一点。

该方法的源代码是available on GitHub..... 实现(比其他答案中的建议更复杂)值得一看:)。

【讨论】:

这不仅是核心,还有框架 4.7.1 及更高版本:docs.microsoft.com/en-us/dotnet/api/… 请注意 Prepend 也存在:docs.microsoft.com/en-us/dotnet/api/… 很好,正是我想要的。很多答案都忽略了这一点,但是您可以简单地返回一个新的可枚举,它首先消耗现有的项目,一旦没有更多的项目,就产生您的额外项目。我认为这就是 Append 所做的。【参考方案5】:

IEnumerableIEnumerable&lt;T&gt; 上的几个简短而甜蜜的扩展方法为我做:

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(System.Collections.Generic.IEnumerable, System.Collections.Generic. IEnumerable)' 有一些无效参数" @TimNewton 你做错了。谁知道为什么,因为没有人可以从错误代码的 sn-p 中分辨出来。 Protip:始终捕获异常并对其执行ToString(),因为它会捕获更多错误详细信息。我猜你文件顶部没有include System.Linq;,但谁知道呢?尝试自己解决,创建一个最小的原型,如果你做不到,它会显示相同的错误,然后在问题中寻求帮助。 我刚刚复制并粘贴了上面的代码。除了 Prepend 给出了我提到的错误之外,所有这些都可以正常工作。它不是运行时异常,而是编译时异常。 @TimNewton:我不知道你在说什么它完美地工作你显然是错误地忽略了编辑中的变化现在我必须在路上了,美好的一天,先生。跨度> 也许我们的环境有些不同。我正在使用 VS2012 和 .NET 4.5。不要再在这上面浪费时间了,我只是想指出上面的代码有问题,尽管是一个小问题。无论如何,我都赞成这个答案,因为它很有用。谢谢。【参考方案6】:

不,IEnumerable 不支持向其中添加项目。替代解决方案是

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

【讨论】:

您的建议不是唯一的选择。例如,ICollection 和 IList 在这里都是合理的选择。 但是你也不能实例化。【参考方案7】:

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

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

【讨论】:

但是你还需要将Concat方法的结果赋值给items对象。 是的,Tomasz 你是对的。我已经修改了最后一个表达式以将连接的值分配给项目。【参考方案8】:

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

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

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

【讨论】:

【参考方案9】:

您不仅不能像您所说的那样添加项目,而且如果您将项目添加到您拥有现有枚举器的List&lt;T&gt;(或几乎任何其他非只读集合),则枚举器无效(从那时起抛出InvalidOperationException)。

如果您正在聚合某种类型的数据查询的结果,您可以使用Concat 扩展方法:

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

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

【讨论】:

【参考方案10】:

其他人已经很好地解释了为什么您不能(也不应该!)能够将项目添加到IEnumerable。我只会补充一点,如果您希望继续对表示集合的接口进行编码并想要添加方法,则应将编码为ICollectionIList。作为额外的财富,这些接口实现了IEnumerable

【讨论】:

【参考方案11】:

你可以这样做。

//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;

【讨论】:

这个问题在 5 年前得到了更完整的回答。将接受的答案作为对问题的良好答案的示例。 最后一行是:items = (IEnumerable)list;【参考方案12】:

最简单的方法就是

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 接口

【讨论】:

【参考方案13】:

实现 IEnumerable 和 IEnumerator(从 IEnumerable 返回)的实例没有任何允许更改集合的 API,该接口提供只读 API。

实际改变集合的两种方法:

    如果实例碰巧是某个带有写入 API 的集合(例如 List),您可以尝试转换为这种类型:

IList&lt;string&gt; list = enumerableInstance as IList&lt;string&gt;;

    从 IEnumerable 创建一个列表(例如,通过 LINQ 扩展方法toList()

var list = enumerableInstance.toList();

【讨论】:

【参考方案14】:

很抱歉重新提出一个非常老的问题,但由于它被列在第一个谷歌搜索结果中,我认为有些人一直在这里登陆。

在很多答案中,其中一些非常有价值且解释清楚,我想补充一点 vue 的不同之处,因为对我来说,问题还没有得到很好的识别。

您正在声明一个存储数据的变量,您需要它能够通过向其中添加项目来进行更改吗?所以你不应该使用将它声明为 IEnumerable。

由@NightOwl888 提议

对于此示例,只需声明 IList 而不是 IEnumerable: IList items = new T[]new T("msg"); items.Add(new T("msg2"));

试图绕过声明的接口限制仅表明您做出了错误的选择。 除此之外,所有建议用于实现其他实现中已经存在的东西的方法都应该被取消考虑。 允许您添加项目的类和接口已经存在。为什么总是重新创建其他地方已经完成的事情?

这种考虑是在接口中抽象变量功能的目标。

TL;DR : IMO 这些是做你需要的最干净的方法:

// 1st choice : Changing declaration
IList<T> variable = new T[]  ;
variable.Add(new T());

// 2nd choice : Changing instantiation, letting the framework taking care of declaration
var variable = new List<T>  ;
variable.Add(new T());

当您需要将变量用作 IEnumerable 时,您将能够做到。当您需要将它用作数组时,您将能够调用“ToArray()”,它真的应该总是那么简单。无需扩展方法,仅在真正需要时进行强制转换,能够在变量上使用 LinQ,等等...

停止做奇怪和/或复杂的事情,因为你只是在声明/实例化时犯了一个错误。

【讨论】:

投了反对票,因为在许多实际情况下,您无法控制正在使用的变量的类型......例如IEnumerable 是许多数据访问库函数的常见返回类型。这不能回答问题,也不能令人满意地证明问题没有实际意义。【参考方案15】:

当然,你可以(我将把你的 T 业务放在一边):

public IEnumerable<string> tryAdd(IEnumerable<string> items)

    List<string> list = items.ToList();
    string obj = "";
    list.Add(obj);

    return list.Select(i => i);

【讨论】:

我不知道为什么这个答案有 8 个反对票,我发现这个解决方案有效而且不错。

以上是关于如何将项目添加到 IEnumerable<T> 集合?的主要内容,如果未能解决你的问题,请参考以下文章

将 IEnumerable<T> 转换为 List<T>

如何将两个 IEnumerable<T> 连接成一个新的 IEnumerable<T>?

如何订购 IEnumerable<T> 以使特殊项目始终位于底部? [复制]

使用 IEnumerable<T> 在 .net core 中添加对象的实例

C#中如何从 IEnumerable<T> 中取值?

如何将 IEnumerable<t> 或 IQueryable<t> 转换为 EntitySet<t>?