我经常发现自己想要在 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 中实现?
从技术上讲,你的头部将是 .First(),你的尾部将是 .Skip(1)。但也许你可以找到更好的解决方案?喜欢在 IEnumerable 上使用 .Aggregate() 吗?
鉴于
IEnumerable<T>
的接口,性能并不总是能得到保证。
您注意到大多数函数式编程语言都实现了 tail 和 head。但应该注意的是,这些语言是在内存结构中起作用的。
IEnumerable<T>
没有任何此类约束,因此不能假设这会是有效的。
例如,一个常见的功能模式是在集合的 Head 上递归工作,然后在调用的 Tail 上递归......
如果您使用实体框架执行此操作,您将向 SQL 服务器发送以下(元)调用,紧密循环。
Select * from
(
Select * from
(
Select * from
(...)
Skip 1
)
Skip 1
);
这将是非常低效的。
编辑:
想一想。另一个原因是,C#/VB.Net 不支持尾递归,因此,这种模式很容易导致
StackOverflow
。
我并不是建议在生产代码中使用它,但我试图向一些了解 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
(可能仍然涉及迭代器)的情况下做到这一点,但我还没有偶然发现它。