为什么 Linq 没有 Head 和 Tail?

问题描述 投票:0回答:4

我经常发现自己想要在 IEnumerables 上使用 Head 和 Tail 方法,而这些方法并不作为 Linq 的一部分存在。虽然我可以轻松地编写自己的内容,但我想知道它们是否被故意排除在外。例如,

var finalCondition = new Sql("WHERE @0 = @1", conditions.Head().Key, conditions.Head().Value);
foreach (var condition in conditions.Tail())
{
  finalCondition.Append("AND @0 = @1", condition.Key, condition.Value);
}

那么,Linq 的最佳实践是什么?我不断寻找其用途这一事实是否表明我没有做推荐的事情?如果不是,那么为什么这个常见的功能范例没有在 Linq 中实现?

c# linq head tail
4个回答
17
投票

从技术上讲,你的头部将是 .First(),你的尾部将是 .Skip(1)。但也许你可以找到更好的解决方案?喜欢在 IEnumerable 上使用 .Aggregate() 吗?


8
投票

鉴于

IEnumerable<T>
的接口,性能并不总是能得到保证。

您注意到大多数函数式编程语言都实现了 tail 和 head。但应该注意的是,这些语言是在内存结构中起作用的。

IEnumerable<T>
没有任何此类约束,因此不能假设这会是有效的。

例如,一个常见的功能模式是在集合的 Head 上递归工作,然后在调用的 Tail 上递归......

如果您使用实体框架执行此操作,您将向 SQL 服务器发送以下(元)调用,紧密循环。

Select * from
(
    Select * from
    (
         Select * from
         (...)
         Skip 1
    )
    Skip 1
);

这将是非常低效的。

编辑:

想一想。另一个原因是,C#/VB.Net 不支持尾递归,因此,这种模式很容易导致

StackOverflow


1
投票

因为“头尾”概念用于函数式编程中的模式匹配和递归调用。 由于C#不支持模式匹配,所以不需要实现head()和tail()方法。

let rec sum = function
  | [] -> 0
  | h::t -> h + sum t

对于你的情况 - 你应该使用Aggregate方法。


0
投票

我并不是建议在生产代码中使用它,但我试图向一些了解 C# 的朋友解释 Haskell 代码片段,并发现我可以通过混合

IEnumerable<T>
IEnumerator<T>
来实现这一点(其中你可以,因为
IEnumerable
总是给你
GetEnumerator()
并且你可以使用迭代器 (
IEnumerator
) 将
yield return
转换为 IEnumerable。迭代器为你提供了模拟惰性的好处(在某些情况下,如果使用正确的话)。

IEnumerable<int> sieve(IEnumerator<int> l)
{
    var (p, xs) = getHeadAndTail(l);
    yield return p;
    foreach (var n in sieve(xs.Where(i => i % p > 0).GetEnumerator()))
        yield return n;
}

(T, IEnumerable<T>) getHeadAndTail<T>(IEnumerator<T> l)
{
    l.MoveNext();
    var head = l.Current;
    return (head, tailGetter());

    IEnumerable<T> tailGetter()
    {
        while (l.MoveNext())
            yield return l.Current;
    }
}

(对于纯粹主义者来说 - 在原始上下文中,这些都是局部函数,因此它们以小写字母开头)

我怀疑有一种方法可以在不(明确)引入

IEnumerator
(可能仍然涉及迭代器)的情况下做到这一点,但我还没有偶然发现它。

© www.soinside.com 2019 - 2024. All rights reserved.