有没有一种 Linq 方法可以向 IEnumerable<T> 添加单个项?

96

我正在尝试做这样的事情:

image.Layers

有时候,我只想要一个不包括Parent层的所有层的IEnumerable<Layer>,可以这样做:

image.Layers.With(image.ParentLayer);

因为与通常使用的image.Layers相比,它仅在少数地方使用。这就是为什么我不想再创建另一个返回Parent层的属性。

12个回答

83

一种方法是将该项(例如数组)创建为单例序列,然后使用Concat将其连接到原始序列中:

image.Layers.Concat(new[] { image.ParentLayer } )

如果您经常这样做,请考虑编写一个Append(或类似的)扩展方法,例如此处列出的一个,它将使您可以执行以下操作:
image.Layers.Append(image.ParentLayer)

.NET Core 更新根据下面的“最佳”答案):

Append and Prepend have now been added to the .NET Standard framework, so you no longer need to write your own. Simply do this:

image.Layers.Append(image.ParentLayer)

50

1
使用这种方法相对于使用.Concat(new[] { ... })有一个缺点:如果要添加的项也实现了IEnumerable<T>,那么Append方法将默认添加IEnumerable<T>,而你可能只是想添加T - extremeandy
1
请注意,这并不作用于原始图像。您需要执行 image.Layers = image.Layers.Append(image.ParentLayer)(可能还需要执行 image.Layers = image.Layers.Append(image.ParentLayer).ToArray() 或类似操作)才能获得预期的效果。 - ruffin

27

编辑

正如@cpb所正确提到的:

现在附加和前置已经默认提供。

(源代码)

微软还决定实现一种在开头或结尾添加项目的方法。他们创建了一个AppendPrepend1Iterator类,它具有一些优化(例如,如果原始基础集合是ICollection,则获取计数)

出于历史原因,我将保留我的答案。


已经给出了许多不同的实现方式。

我的看起来有点不同(但效果一样好)

此外,我发现控制顺序也很实用。因此,通常我还有一个ConcatTo方法,在前面放置新元素。

public static class Utility
{
    /// <summary>
    /// Adds the specified element at the end of the IEnummerable.
    /// </summary>
    /// <typeparam name="T">The type of elements the IEnumerable contans.</typeparam>
    /// <param name="target">The target.</param>
    /// <param name="item">The item to be concatenated.</param>
    /// <returns>An IEnumerable, enumerating first the items in the existing enumerable</returns>
    public static IEnumerable<T> ConcatItem<T>(this IEnumerable<T> target, T item)
    {
        if (null == target) throw new ArgumentException(nameof(target));
        foreach (T t in target) yield return t;
        yield return item;
    }

    /// <summary>
    /// Inserts the specified element at the start of the IEnumerable.
    /// </summary>
    /// <typeparam name="T">The type of elements the IEnumerable contans.</typeparam>
    /// <param name="target">The IEnummerable.</param>
    /// <param name="item">The item to be concatenated.</param>
    /// <returns>An IEnumerable, enumerating first the target elements, and then the new element.</returns>
    public static IEnumerable<T> ConcatTo<T>(this IEnumerable<T> target, T item)
    {
        if (null == target) throw new ArgumentException(nameof(target));
        yield return item;
        foreach (T t in target) yield return t;
    }
}

或者,您可以使用隐式创建的数组(使用 params 关键字),以便您可以调用该方法一次添加一个或多个项目:

public static class Utility
{
    /// <summary>
    /// Adds the specified element at the end of the IEnummerable.
    /// </summary>
    /// <typeparam name="T">The type of elements the IEnumerable contans.</typeparam>
    /// <param name="target">The target.</param>
    /// <param name="items">The items to be concatenated.</param>
    /// <returns>An IEnumerable, enumerating first the items in the existing enumerable</returns>
    public static IEnumerable<T> ConcatItems<T>(this IEnumerable<T> target, params T[] items) =>
        (target ?? throw new ArgumentException(nameof(target))).Concat(items);

    /// <summary>
    /// Inserts the specified element at the start of the IEnumerable.
    /// </summary>
    /// <typeparam name="T">The type of elements the IEnumerable contans.</typeparam>
    /// <param name="target">The IEnummerable.</param>
    /// <param name="items">The items to be concatenated.</param>
    /// <returns>An IEnumerable, enumerating first the target elements, and then the new elements.</returns>
    public static IEnumerable<T> ConcatTo<T>(this IEnumerable<T> target, params T[] items) =>
        items.Concat(target ?? throw new ArgumentException(nameof(target)));

1
.NET 可能会将 yield 语句编译为一个匿名对象,该对象被实例化。 - Jorrit Schippers
1
@Jorrit:实际上,编译器创建了一个(“匿名”)迭代器,这也会带来一些开销。因此,编译后的代码更大,但在运行时,分配的内存只有几个字节,而不是列表的完全副本。 - realbart

13

没有一种单一的方法可以做到这一点。最接近的是Enumerable.Concat方法,但它试图将一个IEnumerable<T>与另一个IEnumerable<T>组合在一起。您可以使用以下方法使其与单个元素配合使用。

image.Layers.Concat(new [] { image.ParentLayer });

或者只需添加一个新的扩展方法

public static IEnumerable<T> ConcatSingle<T>(this IEnumerable<T> enumerable, T value) {
  return enumerable.Concat(new [] { value });
}

8
你可以使用 Enumerable.Concat 方法:
var allLayers = image.Layers.Concat(new[] {image.ParentLayer});

6

I once made a nice little function for this:

public static class CoreUtil
{    
    public static IEnumerable<T> AsEnumerable<T>(params T[] items)
    {
        return items;
    }
}

现在这是可能的:
image.Layers.Append(CoreUtil.AsEnumerable(image.ParentLayer, image.AnotherLayer))

1
自 .Net 3.5 起,AsEnumerable 已成为内置扩展方法,因此可能需要使用不同的名称。此外,AsX 暗示它是一个扩展方法(方法不应该是动词吗?),所以我认为一开始就不是一个好名字。我建议使用 Enumerate - ErikE
哈!很久以前。是的,与此同时我得出了同样的结论。我仍然使用这个小东西,但现在它是ToEnumerableEnumerate也不错。 - Gert Arnold
我仍然认为 ToEnumerable 建议使用扩展方法。那么 CreateEnumerable 怎么样? :) - ErikE

6
您可以做类似于以下的操作:
image.Layers.Concat(new[] { image.ParentLayer });

将枚举与包含要添加的元素的单元素数组连接起来。

3

如果你喜欢.With的语法,可以把它写成扩展方法。IEnumerable不会注意到另一个扩展方法。


3

我使用以下扩展方法来避免创建无用的Array

public static IEnumerable<T> ConcatSingle<T>(this IEnumerable<T> enumerable, T value) {
   return enumerable.Concat(value.Yield());
}

public static IEnumerable<T> Yield<T>(this T item) {
    yield return item;
}

+1 不错的实现,但为什么要避免使用数组呢?我知道创建数组感觉不对,但它真的比 C# 为这些迭代器所做的所有隐藏工作更低效吗? - dss539
1
Yield会分配和返回一个匿名的IEnumerable<T>,它持有对您的项的引用,因此这可能比单个项数组需要更多的内存和时间。 - Jim Balter

1

有一个Concat方法,可以连接两个序列。


3
第二个元素不是一个序列……这就是问题所在。 - nicodemus13

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