C# 为什么林克没有头和尾巴?

C# 为什么林克没有头和尾巴?,c#,linq,head,tail,C#,Linq,Head,Tail,我经常发现自己想要在IEnumerables上使用Head和Tail方法,这在Linq中是不存在的。虽然我可以很容易地写出自己的作品,但我想知道这些作品是否被有意地遗漏了。比如说, var finalCondition = new Sql("WHERE @0 = @1", conditions.Head().Key, conditions.Head().Value); foreach (var condition in conditions.Tail()) { finalCondition.

我经常发现自己想要在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中实现呢?

从技术上讲,你的头会是,你的尾巴会是。但也许你能找到更好的解决办法?就像在IEnumerable上使用一样?

因为“Head&Tail”概念用于函数编程和递归调用。 由于C#不支持模式匹配,因此不需要实现head()和tail()方法

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

至于您的情况-您应该使用方法。

鉴于
IEnumerable
的接口,性能不能总是得到保证

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

IEnumerable
没有任何此类约束,因此不能假设这是有效的

例如,一种常见的函数模式是递归地处理集合的头部,然后在调用的尾部递归

例如,如果您使用EntityFramework执行此操作,您将向SQL server发送以下(meta)调用,即紧密循环

Select * from
(
    Select * from
    (
         Select * from
         (...)
         Skip 1
    )
    Skip 1
);
这将是非常低效的

编辑:


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

First()和Last()或FirstOrDefault(),Oliver Sturm的书中有很多可下载的源代码。如果我还记得的话,这也包含了头和尾的扩展方法。@ GeltTaNod我已经实现了它们,问题更多的是这是否被认为是最佳实践。我明白,这就是为什么我不认为这是一个答案:)。但这本书也详细阐述了函数式编程技术的实际应用和C#中的具体限制。这可能会让你走得更远。(我读了一会儿。)@ServéLaurijssen,列表的尾部不是
Last()
。尾部是没有第一个元素的列表。请参阅、和。@MatthewWatson否,
tail
不是最后一个元素。@MatthewWatson是。“头尾”故事的意义在于,你不需要先打电话给“头尾”,而是将列表拆分成一个概念。nvoigt说得对,OP的代码相当于调用first/skip(1),这不是使用head/tail的好例子。
.skip(1)
在技术上与
tail
不同,因为它仍然计算第一项,然后丢弃它,例如
list.Select(mapper)。skip(1)
将运行
mapper(list([0])
好吧,虽然
.Skip(1)
确实计算了第一个元素,但在您的示例中运行
mapper(list([0]))
是因为没有按照正确的顺序执行。执行
列表。跳过(1)。选择(映射器)
将不会在第一个元素上运行
mapper
。此答案不正确且不相关。它不应该被接受。一个
IEnumerable
尾部很容易被访问,并且无论结构是否在内存中,都不会出现性能上的意外
IEnumerable
类被设计为一次移动一个元素,这就是
Skip(1)
所做的一切。只有当您需要计算一个
IEnumerable
,或跳到任意“索引”中的一个元素时,才会出现性能惊喜。@kdbanman skip和Enumerator.GetNext()是不同的事情。通常,您是对的。Skip(N)的最坏情况是在内部调用GetNext()N次,但让我们假装它是。即使在最坏的情况下,这也不是问题。Skip(1)返回OP请求的尾部,Skip(1)只对GetNext()进行一次延迟调用。是
IEnumerable
Skip
扩展方法的实际.NET实现。我的观点很容易理解。Skip(1)只对MoveNext()进行一次延迟调用,MoveNext()是GetNext()的bool返回版本。请稍候。在什么意义上C#/VB.Net不支持尾部递归?我可能在这里遗漏了一些东西,但是尾部调用优化已经成为.NET的一部分有一段时间了(也许从1.0开始?)。引用大卫·布罗曼的话:“。我可以明确地回忆起至少一次调试会话,当时我想“我是如何使用那个返回地址到达这个堆栈帧的?哦,是的-尾部调用省略”)