我的问题作为以上标题。例如,
1 2
| IEnumerable <T > items = new T []{new T ("msg")};
items .ToList().Add(new T ("msg2")); |
但毕竟里面只有一件东西。
我们能有一个像items.Add(item)这样的方法吗?
像List一样
- IEnumerable仅用于查询集合。它是LINQ框架的主干。它总是对其他一些集合的抽象,如Collection、List或Array。接口只提供一个GetEnumerator方法,该方法返回IEnumerator的一个实例,该实例允许一次遍历扩展集合的一个项。LINQ扩展方法受此接口的限制。IEnumerable设计为只读,因为它可能表示多个集合的部分聚合。
- 对于这个例子,只需声明IList而不是IEnumerable:IList items = new T[]{new T("msg")}; items.Add(new T("msg2"));
您不能,因为IEnumerable不一定表示可以向其中添加项的集合。实际上,它根本不一定代表一个集合!例如:
1 2 3 4 5 6 7 8 9 10 11 12
| IEnumerable<string> ReadLines()
{
string s;
do
{
s = Console.ReadLine();
yield return s;
} while (s !="");
}
IEnumerable<string> lines = ReadLines();
lines.Add("foo") // so what is this supposed to do?? |
但是,您可以创建一个新的IEnumerable对象(未指定类型),在枚举时,它将提供旧对象的所有项以及您自己的一些项。您可以使用Enumerable.Concat进行以下操作:
1
| items = items .Concat(new[] {"foo" }); |
这不会更改数组对象(无论如何,不能将项插入到数组中)。但它将创建一个新的对象,该对象将列出数组中的所有项,然后是"foo"。此外,该新对象将跟踪数组中的更改(即,每当枚举它时,都会看到项的当前值)。
- 创建一个数组来添加单个值的开销有点高。为单个值concat定义一个扩展方法更为有用。
- 这取决于-也许值得,但我想称之为过早优化,除非在循环中反复调用Concat;如果是这样,那么无论如何,您都会遇到更大的问题,因为每次调用Concat时,您都会得到一个更多的枚举器层-因此,在它结束时,对MoveNext的单个调用将导致一系列调用长度等于迭代次数。
- "帕维尔,呵呵。通常让我困扰的不是创建数组的内存开销。我觉得讨厌的是额外的打字。
- 所以我更了解IEnumerable的作用。它应该只是视图,不打算修改。谢谢大家
- 我有一个针对c中IEnumerable的语法糖的连接功能请求,包括将operator+重载为Concat(顺便说一句,您知道在vb中,您可以将IEnumerable索引为数组,就像它使用ElementAt在引擎盖下一样:connect.microsoft.com/VisualStudio/Feedback/&hellip;。如果我们在左边和右边多加两个Concat,就可以减少xs +=x 的输入,所以在c 5.0:)中用poke-mads(torgersen)来优先排序。
- @Pavel,肯定知道vb.net的特性(在调试器中遇到过几次跟踪bug)。在特性方面,插入BCL团队以让他们添加扩展方法可能更快(C和BCL是单独的团队)。
- 值得一提的是,concat本身就是一个扩展方法。除非您包含System.Linq,否则它将不可用。
IEnumerable型不支持这种操作。IEnumerable接口的目的是允许使用者查看集合的内容。不要修改值。
当您执行像.to list().add()这样的操作时,您将创建一个新的List,并向该列表中添加一个值。它与原始列表没有连接。
您所能做的就是使用添加扩展方法创建一个具有附加值的新IEnumerable。
1
| items = items.Add("msg2"); |
即使在这种情况下,它也不会修改原始的IEnumerable对象。这可以通过保存对它的引用来验证。例如
1 2 3
| var items = new string[]{"foo"};
var temp = items ;
items = items .Add("bar"); |
在这组操作之后,变量temp仍将只引用值集中单个元素"foo"的可枚举值,而项将引用值为"foo"和"bar"的不同可枚举值。
编辑
我经常忘记,add不是IEnumerable上的典型扩展方法,因为它是我最终定义的第一个方法之一。这里是
1 2 3 4 5 6
| public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value) {
foreach ( var cur in e) {
yield return cur;
}
yield return value;
} |
- 那叫Concat。
- @帕维尔,谢谢你的指点。即使是concat也不能工作,因为它需要另一个ienumerable。我粘贴了我在项目中定义的典型扩展方法。
- 不过,我不会称之为Add,因为Add实际上在任何其他.NET类型(不仅仅是集合)上都会改变集合的位置。可能是With?或者它甚至可能只是Concat的另一个过载。
- @帕维尔,这件事我来来回回地谈了几次。大多数时候我都坚持添加,因为这只是我想到的第一件事。我很少发现它与变异的加法混淆。
- 在我问答案之前,我建立了这种静态的谅解。我认为IEnumerable是一个可以修改的集合,但它的意图是只查看,对吗?
- 我同意Concat过载可能是更好的选择,因为Add通常意味着易变性。
- 把尸体改成return e.Concat(Enumerable.Repeat(value,1));怎么样?
- 命名它Add的问题是,List.Add在改变原始列表时不返回List,而扩展方法IEnumerable.Add返回IEnumerable但不改变原始列表。这种行为差异已经足够了,我认为Add是一个不可取的名字。
- "IEnumerable接口的目的是允许使用者查看集合的内容。不要修改值。",-一个闪电。
您是否考虑改用ICollection或IList接口,它们的存在正是因为您希望在IEnumerable上使用"添加"方法。IEnumerable用于将一个类型"标记"为……嗯..可枚举的或只是一系列项,而不必保证真正的基础对象是否支持添加/删除项。还请记住,这些接口实现IEnumerable,因此您也可以使用IEnumerable获得所有扩展方法。
在IEnumerable和IEnumerable上有两种简短的、甜蜜的扩展方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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方法给了我一个错误。对象"[]"不包含"concat"的定义和最佳扩展方法重载"system.linq.Enumerable.concat(system.collections.g&zwnj;&8203;eneric.ienumerable,system.collections.generic.ienumerable)的某些参数无效"
- @你做错了。谁知道为什么呢,因为没人能从那一小段错误代码中分辨出来。Protip:总是捕获异常并在其上执行ToString(),因为它捕获了更多的错误细节。我猜你没有在你的文件的顶端,但是谁知道呢?试着自己去弄清楚,创建一个最小的原型,如果不能的话,它会显示同样的错误,然后在一个问题上寻求帮助。
- 我只是复制并粘贴了上面的代码。除了前面提到的错误,所有的都可以工作。它不是运行时异常,而是编译时异常。
- @提姆牛顿:我不知道你在说什么,它工作得很好。很明显,你错了,忽视编辑的变化。现在我必须上路了,祝你好运,先生。
- 也许我们的环境不同。我使用的是VS2012和.NET 4.5。别再浪费时间了,我只是想我会指出上面的代码有问题,尽管是个小问题。不管怎么说,我已经把这个答案否决为有用的。谢谢。
- @蒂姆:(去看编辑)
- @Erike是因为并非所有东西都是通用的,而且实现起来很简单。
不,IEnumerable不支持向其中添加项。
您的"备选方案"是:
1 2
| var List = new List (items );
List .Add(otherItem ); |
- 你的建议不是唯一的选择。例如,iCollection和iList都是合理的选择。
- 但你也不能实例化。
- 当然,你也不能实例化它们——它们是接口。
要添加第二条消息,您需要-
1 2
| IEnumerable <T > items = new T []{new T ("msg")};
items = items .Concat(new[] {new T ("msg2")}) |
- 但您还需要将concat方法的结果分配给items对象。
- 是的,托马斯,你说得对。我修改了最后一个表达式,将连接的值赋给项。
在.NET核心中,有一个方法Enumerable.Append可以做到这一点。
方法的源代码在Github上可用…..实现(比其他答案中的建议更复杂)值得一看。
不仅不能添加您声明的项目,而且如果您向现有枚举器的List中添加了一个项目(或几乎任何其他非只读集合),枚举器将失效(从那时起抛出InvalidOperationException)。
如果要聚合某些类型数据查询的结果,可以使用Concat扩展方法:
编辑:我最初在示例中使用了Union扩展,这并不真正正确。我的应用程序广泛地使用它来确保重叠查询不会复制结果。
1 2 3 4
| IEnumerable<T> itemsA = ...;
IEnumerable<T> itemsB = ...;
IEnumerable<T> itemsC = ...;
return itemsA.Concat(itemsB).Concat(itemsC); |
我来这里只是想说,除了Enumerable.Concat扩展方法之外,在.NET核心1.1.1中似乎还有另一个名为Enumerable.Append的方法。后者允许您将单个项连接到现有序列。所以aamol的答案也可以写成
1 2
| IEnumerable <T > items = new T []{new T ("msg")};
items = items .Append(new T ("msg2")); |
不过,请注意,此函数不会更改输入序列,它只是返回一个包装器,将给定序列和附加项放在一起。
其他人已经对你为什么不能(也不应该)给出了很好的解释。能够在IEnumerable中添加项目。我只想补充一点,如果您希望继续对表示集合的接口进行编码,并且希望使用add方法,那么您应该对ICollection或IList进行编码。作为一个额外的财富,这些接口实现了IEnumerable。
你可以这样做。
1 2 3 4 5 6 7 8 9 10 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;
最简单的方法就是
1 2 3 4
| 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接口
当然,你可以(我把你的T业务放在一边):
1 2 3 4 5 6 7 8
| public IEnumerable<string> tryAdd(IEnumerable<string> items)
{
List<string> list = items.ToList();
string obj ="";
list.Add(obj);
return list.Select(i => i);
} |