有没有一种语句可以在 IEnumerable<T> 中添加一个元素 T?

25
例如:
string element = 'a';
IEnumerable<string> list = new List<string>{ 'b', 'c', 'd' };

IEnumerable<string> singleList = ???; //singleList yields 'a', 'b', 'c', 'd' 

3
在.NET Core中,它是内置的:public static System.Collections.Generic.IEnumerable<TSource> Prepend<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, TSource element); (https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.prepend?view=netcore-2.0) - Niklas Peter
2
作为实现 .Net Standard 2.0 的一部分,它已经包含在 .Net 4.7.1中。 - NetMage
11个回答

35

我猜您不能简单地将元素插入到现有列表中?

好吧,您可以使用 new[] {element}.Concat(list)

否则,您可以编写自己的扩展方法:

    public static IEnumerable<T> Prepend<T>(
            this IEnumerable<T> values, T value) {
        yield return value;
        foreach (T item in values) {
            yield return item;
        }
    }
    ...

    var singleList = list.Prepend("a");

6
@Ian:我认为你指的是自K&R以来的“The One True Brace Style(最佳大括号风格)”。 - Richard
应该使用Concat()而不是Union()吧?Union()方法只会返回不同的元素。 - Lucas
1
有一个打字错误:使用Concat而不是Union。 - jyoung
4
@Ian - 我在论坛上使用这种大括号的样式是为了保留垂直空间。对于实际的代码,我真的不在乎大括号的样式。 - Marc Gravell

8

6
你可以自己打造:

你可以自己动手做:

static IEnumerable<T> Prepend<T>(this IEnumerable<T> seq, T val) {
 yield return val;
 foreach (T t in seq) {
  yield return t;
 }
}

然后使用它:

IEnumerable<string> singleList = list.Prepend(element);

2
我认为在foreach循环内应该是“yield return t”,并且该方法应该是静态的。 - user1047100

5
public static class IEnumerableExtensions
{
    public static IEnumerable<T> Prepend<T>(this IEnumerable<T> ie, T item)
    {
         return new T[] { item }.Concat(ie);
    }
}

4
这样就可以做到了...
IEnumerable<string> singleList = new[] {element}.Concat(list);

如果你想让singleList成为一个List,那么...
IEnumerable<string> singleList = new List<string>() {element}.Concat(list);

也可以这样做。


在你的第二个例子中,singleList 是一个 IEnumerable,而不是 List。 - Lucas
已经为马丁修复了。 - NetMage
但是它被拒绝了。编辑难道不应该有更高的标准吗,比如能够理解代码? - NetMage
我认为你的编辑被拒绝是因为我的第二个示例一个List,但它被强制转换为IEnumerable。底层数据结构是List,但使用IEnumerable接口公开。原始问题想知道如何在IEnumerable中添加前缀,因此保持结果为IEnumerable是有意义的。 - Martin Peck

3
此外:
IEnumerable<string> items = Enumerable.Repeat(item, 1).Concat(list);

2

我发现能够以可链接的方式前置多个项目非常方便。这个版本利用扩展方法和params实现。

需要注意的是,该版本隐式地允许使用null, 但如果需要的行为是抛出NullReferenceException(),那么更改很容易。

public static class IEnumerableExtensions
{
    public static IEnumerable<T> Prepend<T>(this IEnumerable<T> source, params T[] items)
    {
        return items.Concat(source ?? new T[0]);
    }
}

允许针对单个项使用非常易读的语法:

GetItems().Prepend(first, second, third);

...以及用于项目集合:

GetItems().Prepend(GetMoreItems());

完成问题中的示例将产生以下结果:
string element = "a";
IEnumerable<string> list = new List<string>{ "b", "c", "d" };

IEnumerable<string> singleList = list.Prepend(element);

1
你好像在引用source时打错了,但是声明的是sources - NetMage
没问题,这是我最喜欢的 Prepend 形式 - 不幸的是,它不是 .Net Core 和 .Net Standard 2.0 添加的。 - NetMage

1

不,没有这样的内置语句,但实现这样的功能非常简单:

IEnumerable<T> PrependTo<T>(IEnumerable<T> underlyingEnumerable, params T[] values)
{
    foreach(T value in values)
        yield return value;

    foreach(T value in underlyingEnumerable)
        yield return value;
}

IEnumerable<string> singleList = PrependTo(list, element);

如果C#版本允许,您甚至可以将其制作为扩展方法。


如果你将它作为扩展方法,那么你刚刚实现了IEnumerable<T>.Union() :P - Lucas

1

提醒一下 - List< T > 不是唯一的容器类型。如果你发现自己经常在列表前面添加元素,也可以考虑使用 Stack< T > 来实现你的容器。一旦你有了一个栈

var container = new Stack<string>(new string[] { "b", "c", "d" });

您可以始终通过“prepend”元素进行操作。
container.Push("a");

并且仍然可以将集合使用为 IEnumerable< T >,就像在

foreach (var s in container)
    // do sth with s

除了像Pop(),Peek()这样的栈常用方法之外,还有其他一些方法。
以上一些解决方案遍历整个IEnumeration 只是为了在前面添加一个元素(或在某些情况下添加多个元素)。如果您的集合包含大量元素并且频繁添加到前面,则这可能是一种非常昂贵的操作。

0

正如Niklas和NetMage在评论中指出的那样。

C#中有一个新的内置Prepend方法。

enter image description here


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