Linq 新功能 (4) 自订预设值, Zip 与 Index struct, Range struct

.NET 6 在 Linq 上的新增功能真的很多,这一篇聊一些原有方法的多载新增。

本集提要
  • 框架 : .NET 6
  • 功能 : FirstOrDefault, LastOrDefault, SingleOrDefault
  • 功能 : Zip
  • 功能 :ElementAt, ElementAtOrDefault
  • 功能 :Take
自订预设值

以前 FirstOrDefault, LastOrDefault, SingleOrDefault 如果没找到符合条件的结果就只能回传该型别的预设值,就拿 FirstOrDefault 来说吧,以前只有两个多载 ( LastOrDefault, SingleOrDefault 请以此类推):

FirstOrDefault<TSource>(IEnumerable<TSource>, Func<TSource,Boolean>)
FirstOrDefault<TSource>(IEnumerable<TSource>)

.NET 6 之后则多出这两个,让使用者可以自己设定想要的预设值:

FirstOrDefault<TSource>(IEnumerable<TSource>, Func<TSource,Boolean>, TSource)
FirstOrDefault<TSource>(IEnumerable<TSource>, TSource)

好像在哪看过类似的玩意对吧?很久以前 Nullable<T> 有个方法 GetValueOrDefault(T) 就是可以设定自己想要的预设值,我猜大概也是从这来的灵感。不过有另外一个事情让我很疑惑, FirstOrDefault, LastOrDefault, SingleOrDefault 都多加了这类可以自订回传预设值的多载,但 ElementAtOrDefault 却没有!?

以后要设定回传的预设值就可以这样写:

 var source = Enumerable.Range(1, 10);
 var first = source.FirstOrDefault(x => x > 10, int.MinValue);
 var last = source.LastOrDefault(x => x > 10, int.MinValue);
 var single = source.SingleOrDefault(x => x == 11, int.MinValue);
 Console.WriteLine($"First: {first}, Last: {last}, Single: {single}");
Zip

过去 Zip 只能传入两个序列合併,它在 .NET Framework 4.0 初登场的时候只有一种:

IEnumerable<TResult> Zip<TFirst,TSecond,TResult> (this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst,TSecond,TResult> resultSelector);

这个的回传方式是靠传入Func<TFirst,TSecond,TResult> 来组合回传值,例如

 var s1 = new string[] { "A", "B", "C", "D" };
 var numbers = new int[] { 1, 2, 3, 4 };
 var zip1 = s1.Zip(numbers, (a, b) => $"{a}-{b}");

后来大概是觉得用 ValueTuple 很爽,于是到了 .NET Core 3.0 的时候,出了一个回传 ValueTuple 的多载,但此时还是合併两个序列:

IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst,TSecond> (this IEnumerable<TFirst> first, IEnumerable<TSecond> second);
 var s1 = new string[] { "A", "B", "C", "D" };
 var numbers = new int[] { 1, 2, 3, 4 };
 var zip2 = s1.Zip(numbers);

推移到 .NET 6 的时代,又多出一个可以直接组合三个序列的多载:

Enumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst,TSecond,TThird> (this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third);

于是我们可以这么用:

  var s1 = new string[] { "A", "B", "C", "D" };
  var s2 = new string[] { "甲", "乙", "丙", "丁" };
  var numbers = new int[] { 1, 2, 3, 4 }; 
  var zip3 = s1.Zip(numbers, s2);
Index struct in ElementAt

ElementAt 与 ElementAtOrDefault  各别新增一个参数是 Index struct 的多载:

TSource ElementAt<TSource> (this IEnumerable<TSource> source, Index index);
TSource ElementAtOrDefault<TSource> (this IEnumerable<TSource> source, Index index);

这个好处是甚么呢?就是要从尾端往前数的时候可以比较好写。来比较一下 before / after

 // before .NET 6
 var element1b = source.ElementAt(source.Count() - 1);
 var element2b = source.ElementAtOrDefault(source.Count() - 2);

 // after .NET 6
 var element1a = source.ElementAt(^1);            
 var element2a = source.ElementAtOrDefault(^2);
Range struct in Take

在 .NET 6,Take 引进了一个使用 Range struct 作为参数的多载:

Enumerable.Take<TSource>(IEnumerable<TSource>, Range)

这用途和 Skip(int).Take(int) 效果一样,我感觉差异是在应用场景。

  1. 如果需求叙述是以跳过几笔再取几笔用 Skip(int).Take(int) 比较适用。
  2. 如果需求叙述是以取第x笔到第y笔用 Take(Range) 较能彰显意义。【注1】

列出两种方式写法:

 // before .NET 6
  var take1 = source.Skip(2).Take(3);
 // after .NET 6
 var take2 = source.Take(2..5);

这篇文章所有的範例请参考这里。

注1:这样的需求有许多种解法,比方说有可能会先转 ReadOnlySpan<T> 呼叫 Slice,也有可能会用 Array.Copy 或是其他方式,端看整体情境的需求。

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章

5 点赞(415) 阅读(67)