带有单个额外元素的 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' 因为:
- 也许声明一个新数组会对性能造成影响(尽管这可能几乎可以忽略不计)
- 它看起来不像
a.Concat(4)
那样整洁、易读和自然
有人知道为什么不存在这样的过载吗?
此外,假设我的谷歌搜索没有让我失望 - 没有类似的 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 中,他们添加了 Prepend
和 Append
方法来相应地将一个元素添加到可枚举的开头和结尾。
用法:
var emptySequence = Enumerable.Empty<long>();
var singleElementSequence = emptySequence.Append(256L);
如果我们想要单个 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' 因为:
- 也许声明一个新数组会对性能造成影响(尽管这可能几乎可以忽略不计)
- 它看起来不像
a.Concat(4)
那样整洁、易读和自然
有人知道为什么不存在这样的过载吗?
此外,假设我的谷歌搜索没有让我失望 - 没有类似的 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 forIEnumerable
. Therefore, any .NET Framework type that implementsSystem.IEnumerable
can be used as a sequence.
它提供了一个明确的 unit 函数作为 Seq.singleton
。
总结
虽然其中 none 为我们提供了 事实 阐明了 为什么 这些序列构造没有明确说明的原因存在于 c# 中,直到了解设计决策过程的人分享该信息,它确实强调值得了解更多。
在 .NET Framework 4.7.1 中,他们添加了 Prepend
和 Append
方法来相应地将一个元素添加到可枚举的开头和结尾。
用法:
var emptySequence = Enumerable.Empty<long>();
var singleElementSequence = emptySequence.Append(256L);