【问题标题】:LINQ Concatenation with a single extra element带有单个额外元素的 LINQ 连接
【发布时间】:2018-04-06 23:58:29
【问题描述】:

如果我们想要一个 IEnumerable<T> 代表两个 IEnumberable<T>s 的串联,我们可以使用 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})。在我看来,这很好(ish)但有点“不干净”,因为:

  • 声明一个新数组可能会对性能造成影响(尽管这几乎可以忽略不计)
  • 只是看起来不像a.Concat(4)那么简洁、易读和自然

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

另外,假设我的谷歌搜索没有让我失望 - 没有类似的 LINQ 扩展方法采用 T 类型的单个元素。

(我知道滚动自己的扩展方法来产生这种效果非常容易 - 但这不会让这个省略更加奇怪吗?我怀疑它的省略是有原因的,但无法想象它会发生什么是吗?)

更新:

承认以意见为基础的投票结束了这一点 - 我应该澄清一下,我并不是在征求人们对这是否是对 LINQ 的一个很好的补充的意见。

更多我正在寻求了解为什么它不是 LINQ 的一部分的事实原因。

【问题讨论】:

  • 遗漏不一定是有原因的。入选必须有充分的理由。
  • 底线是:我们只能猜测。除非来自 C# 语言规范组的人插话。所以:基于意见,100%。不相关,因为我们不知道。猜测是没有答案的。
  • 它不是 LINQ 的一部分的事实原因只能由语言设计者来回答。
  • “我正在寻求了解为什么它还不是 LINQ 的一部分的事实原因。” -- 这对你来说怎么样?到目前为止,已经发布了六个不同的答案,not one 包含任何关于为什么这不是 LINQ 的一部分的事实信息。他们都是意见。这正是存在这种密切原因的原因;因为这种性质的问题会引起很多意见,但没有事实
  • 我对这个问题没有强烈的看法。我(经常!)鼓励人们做的是不要问“为什么”,或者更糟糕的是,“为什么不”关于 SO 的问题;很难令人满意地回答它们。 “为什么”的答案通常是“设计团队对此进行了数小时/数天/数周的辩论,我们没有那次谈话的记录”,我们已经看到每个“为什么不?”有相同的答案:该功能开始时未实现并留在那里,因为每个人都忙于其他功能。坚持“如何”和“什么”的问题。

标签: c# linq ienumerable


【解决方案1】:

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

用法:

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

【讨论】:

    【解决方案2】:

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

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

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

    • Enumerable.Empty
    • 可枚举。重复
    • 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 函数令人好奇和有趣

    在 Bart De Smet 的博文中有趣的 MoreLINQ series 中,使用 System.Linq.EnumerableEx 说明,帖子 More LINQ with System.Interactive – Sequences under construction 专门处理这个问题,使用以下适当命名的方法来构造单个元素 IEnumerable

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

    这不过是 monad 上使用的返回函数(有时称为 unit)。

    Eric Lippert 在monads 上的博客系列也很有趣,其中引用了part eight 中的以下引用:

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

    坦率地说,最后一个有点狡猾。我希望Enumerable 上有一个专门用于制作单元素序列的静态方法。

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

    序列由seq&lt;'T&gt; 类型表示,它是IEnumerable 的别名。因此,任何实现System.IEnumerable 的 .NET Framework 类型都可以用作序列。

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

    结论

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

    【讨论】:

    • “如果包含一个额外的重载作为语法糖可能会很好” -- 为什么? Enumerable.FromElement(x) 会比 new [] { x } 更好吗?
    • @PeterDuniho 我并不是说它更好。不过,它会明确意图:我想用单个元素实例化 IEnumerable&lt;T&gt;,而不是必须选择特定的 IEnumerable&lt;T&gt; 实现,例如数组。推理与我更喜欢使用IEnumerable.Empty&lt;int&gt; 而不是new int[0];new int[] {} 的事实没有太大区别,(与静态实例优化原因无关)。还有人可能会受益于未来潜在的针对单个元素序列的编译器技术优化,因为我们隐藏了实现。
    • @PeterDuniho,我在我的回答中添加了一些额外的信息,以澄清为什么如果存在这样的生成器函数它不会很奇怪,因为它是 IEnumerable monad 的最基本构造函数.
    • @PeterDuniho:前者无法转换为唯一元素可能发生突变的数组;后者可以。
    【解决方案3】:

    首先 - 你的谷歌搜索很好 - 没有这样的方法。不过我喜欢这个主意。这是一个用例,如果你遇到它,那就太好了。

    我怀疑它没有包含在 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 方法类似地实现(它们实际上返回一个内部迭代器,而不是直接产生)
    • 参数检查不会伤害它

    【讨论】:

      【解决方案4】:

      哲学问题,我喜欢。

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

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

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

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

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

      【讨论】:

      • 在修改事物方面,为什么连接单个元素与连接整个系列有什么不同?
      • 对此我恐怕没有明确的答案。我能找到的唯一原因是,虽然您可能确实(例外地)连接了 2 个可枚举,但添加单个元素永远不会发生,因为 IEnumerable 是不可变的。如果要将单个元素添加到 IEnumerable,也许您应该考虑 IList 实现。微软工程师可能想要强调这个概念,省略单一的 add 方法。你觉得有道理吗?
      猜你喜欢
      • 2017-07-31
      • 1970-01-01
      • 2018-02-26
      • 2021-09-23
      • 1970-01-01
      • 1970-01-01
      • 2015-03-12
      • 2013-09-11
      • 1970-01-01
      相关资源
      最近更新 更多