带有单个额外元素的 LINQ 串联

LINQ Concatenation with a single extra element

如果我们想要单个 IEnumerable<T> 表示两个 IEnumberable<T> 的串联,我们可以使用 LINQ Concat() 方法。

例如:

int[] a = new int[] { 1, 2, 3 };
int[] b = new int[] { 4, 5 };

foreach (int i in a.Concat(b))
{
    Console.Write(i);
}

当然输出12345.

我的问题是,为什么没有 Concat() 的重载只是接受一个 T 类型的元素,这样:

int[] a = new int[] { 1, 2, 3 };

foreach (int i in a.Concat(4))
{
    Console.Write(i);
}

将编译并产生输出:1234?

围绕该问题进行谷歌搜索会引发几个 SO 问题,其中公认的答案表明,寻求实现这一目标的最佳方法是简单地执行 a.Concat(new int[] {4})。这很好(大概),但在我看来有点 'unclean' 因为:

有人知道为什么不存在这样的过载吗?

此外,假设我的谷歌搜索没有让我失望 - 没有类似的 LINQ 扩展方法采用 T.

类型的单个元素

(我知道滚动自己的扩展方法来产生这种效果是非常容易的——但这不是让遗漏变得更加奇怪吗?我怀疑遗漏是有原因的,但无法想象是什么可能是?)

更新:

承认有几票将此关闭为基于意见 - 我应该澄清一下,我不是在征求人们对这是否是 LINQ 的一个很好的补充的意见。

更多 我正在寻求了解它尚未成为 LINQ 一部分的实际原因。

哲学问题,我喜欢。

首先,您可以使用扩展方法轻松创建该行为

public static IEnumerable<TSource> Concat(this IEnumerable<TSource> source, TSource element)
{
   return source.Concat(new[]{element});
}

我认为核心问题是 IEnumerable 是一个不可变的接口,不能即时修改。

这可能(我使用 could 因为我不在 Microsoft 工作,所以我可能完全错了)是 IEnumerable 的 modify 部分不是很好的原因 developed(意思是你缺少一些方便的方法)。

如果您必须修改该集合,请考虑使用 List 或其他接口。

首先 - 你的谷歌搜索没问题 - 没有这样的方法。不过我喜欢这个主意。这是一个用例,如果您 运行 加入,拥有它会很棒。

我怀疑它没有包含在 LINQ API 中,因为设计者没有看到对它的足够普遍的需求。不过这只是我的猜想。

您说得对,创建仅包含一个元素的数组并不是那么直观。您可以获得想要的感觉和性能:

public static class EnumerableExtensions {
   public static IEnumerable<T> Concat<T>(this IEnumerable<T> source, T element) {
        foreach (var e in source) {
            yield return e; 
        }
        yield return element;
    }

    public static IEnumerable<T> Concat<T>(this T source, IEnumerable<T> element) {
        yield return source; 
        foreach (var e in element) {
            yield return e;
        }

    }
}

class Program
{
    static void Main()
    {
        List<int> ints = new List<int> {1, 2, 3}; 
        var startingInt = 0; 

        foreach (var i in startingInt.Concat(ints).Concat(4)) {
            Console.WriteLine(i);
        }
    }
}

输出:

0
1
2
3
4
  • 惰性评估
  • 与内置 LINQ 方法类似地实现(它们实际上 return 一个内部迭代器,而不是直接产生)
  • 参数检查不会伤害它

包含(以一种或另一种形式)的一个很好的理由是 IEnumerables 更像是功能序列单子。

但由于 LINQ 直到 .NET 3.0 才出现,并且主要使用扩展方法实现,我可以想象他们省略了处理单个元素的扩展方法 T。这仍然是我的纯粹猜测。

但是它们确实包含生成器函数,它们不是扩展方法。具体如下:

  • Enumerable.Empty
  • Enumerable.Repeat
  • Enumerable.Range

您可以使用这些而不是自制扩展方法。你提到的两个用例,可以解决为:

int[] a = new int[] { 1, 2, 3 };

var myPrependedEnumerable = Enumerable.Repeat(0, 1).Concat(a);
var myAppendedEnumerable = a.Concat(Enumerable.Repeat(4, 1));

如果包含额外的重载作为语法糖可能会更好。

Enumerable.FromElement(x); // or a better name (see below).

缺少明确的 Unit 函数很奇怪也很有趣

在有趣的MoreLINQ series of blog posts by Bart De Smet, illustrated using the System.Linq.EnumerableEx, the post More LINQ with System.Interactive – Sequences under construction中专门处理了这个问题,使用以下适当命名的方法构造单个元素IEnumerable

public static IEnumerable<TSource> Return<TSource>(TSource value);

This is nothing but the return function (sometimes referred to as unit) used on a monad.

Eric Lippert 在 monads, which features the following quote in part eight 上的博客系列也很有趣:

IEnumerable<int> sequence = Enumerable.Repeat<int>(123, 1);

And frankly, that last one is a bit dodgy. I wish there was a static method on Enumerable specifically for making a one-element sequence.

此外,F#语言提供了seq类型:

Sequences are represented by the seq<'T> type, which is an alias for IEnumerable. Therefore, any .NET Framework type that implements System.IEnumerable can be used as a sequence.

它提供了一个明确的 unit 函数作为 Seq.singleton

总结

虽然其中 none 为我们提供了 事实 阐明了 为什么 这些序列构造没有明确说明的原因存在于 c# 中,直到了解设计决策过程的人分享该信息,它确实强调值得了解更多。

在 .NET Framework 4.7.1 中,他们添加了 PrependAppend 方法来相应地将一个元素添加到可枚举的开头和结尾。

用法:

var emptySequence = Enumerable.Empty<long>();
var singleElementSequence = emptySequence.Append(256L);