假设我有一个IEnumerable<X>
,其中 的 一些实例X
可以转换为Y
,而有些则不能,并且想要一个IEnumerable<Y>
, 只包含那些X
可以转换为 的 es Y
。
例如,如果我有一个IEnumerable<int?>
同时包含空值和非空值的 ,我可能需要一个IEnumerable<int>
仅包含非空值的。
注意:请不要int?
过于字面地理解这个例子;该解决方案应该适用于任何X
and ,其中和Y
之间的差异不仅仅是类型差异或可空性差异。X
Y
我可以做到的一种方法如下:
public static void Main( string[] args )
{
int?[] a = { null, 42, null, 5 };
IEnumerable<int> ints = a //
.Where( i => i.HasValue ) //
.Select( i => i!.Value );
foreach( var i in ints )
Console.WriteLine( i );
Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)
虽然上面的代码有效,但我想将Select()
and合并Where()
到一个语句中。这样做可以避免丑陋的i!
. 它还可以一步完成转换和过滤,从而利用过滤期间完成的工作来减少转换期间所需的工作量。
有办法实现吗?
Mic*_*Liu 17
您可以编写自己的扩展方法来组合Select
和Where
:
public static IEnumerable<TResult> SelectWhere<TSource, TResult>(
this IEnumerable<TSource> source, Func<TSource, (bool, TResult)> selector)
{
foreach (TSource item in source)
if (selector(item) is (true, var result))
yield return result;
}
Run Code Online (Sandbox Code Playgroud)
对于每个输入值,如果应包含该值或应排除该值,selector
则应返回。(true, transformedValue)
(false, default)
举个例子,给定一个数组int?[] a
,语句
IEnumerable<int> negatedInts = a
.Where(i => i.HasValue)
.Select(i => -i!.Value);
Run Code Online (Sandbox Code Playgroud)
可以这样重写:
IEnumerable<int> negatedInts = a
.SelectWhere(i => i is int value ? (true, -value) : (false, default));
Run Code Online (Sandbox Code Playgroud)
如果您只想按类型过滤值而不以其他方式转换值,则可以使用 LINQ OfType方法:
IEnumerable<int?>
, 则.OfType<int>()
返回IEnumerable<int>
并排除空值。IEnumerable<Base>
, 则.OfType<Derived>()
返回IEnumerable<Derived>
并排除空引用和其他类型的对象。可以使用 LINQ 的SelectMany方法一步完成,例如:
\nIEnumerable<int> ints = a\n .SelectMany(e => e switch\n {\n int i => new[] { i },\n _ => Enumerable.Empty<int>()\n });\n
Run Code Online (Sandbox Code Playgroud)\n这是有效的,因为允许我们为输入序列的每个元素SelectMany
创建一个新的元素序列(可能是不同类型) (然后将其简单地连接起来以产生最终结果)。在这里,我们为要包含的每个元素返回一个单元素序列,为要丢弃的每个元素返回空序列。SelectMany
为了好玩,如果我们更深入地研究一下理论,LINQ 是monad的一个例子,它SelectMany
服务于 monad 的操作bind
(>>=
Haskell 中的运算符)。这意味着实际上我们应该能够仅使用方法来实现 LINQ 的所有SelectMany
方法。
注意:上述实现可能不是最有效的方法,因为我们为要包含在结果序列中的每个元素分配一个新的单元素数组。事实上,该new[] { i }
表达式 if monad 的return
运算符在 LINQ 中没有相应的方法。不过,我们可以轻松地自己实现它(只是为了好玩):
public static class EnumerableExtension\n{\n public static IEnumerable<T> Return<T>(this T elem)\n {\n yield return elem;\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n这Return
甚至可能提高我们的实施效率:
IEnumerable<int> ints = a\n .SelectMany(e => e switch\n {\n int i => i.Return(),\n _ => Enumerable.Empty<int>()\n });\n
Run Code Online (Sandbox Code Playgroud)\n或者没有扩展方法:
\nIEnumerable<int> ints = a\n .SelectMany(e => e switch\n {\n int i => Enumerable.Empty<int>().Prepend(i),\n _ => Enumerable.Empty<int>()\n });\n
Run Code Online (Sandbox Code Playgroud)\n在 @MichaelLiu评论之后,我运行了一个快速基准测试,结果表明,与我的预期相反,在内存和性能方面new[] { i }
确实比我尝试的任何其他方法都更有效(特别是在内存分配方面),而原始方法和方法都更有效:Return
SelectWhere
方法 | 意思是 | 错误 | 标准差 | 0代 | 已分配 |
---|---|---|---|---|---|
原来的 | 9.462\xce\xbcs | 0.0094 \xce\xbcs | 0.0078 \xce\xbcs | 0.0153 | 104乙 |
选择地点 | 9.963\xce\xbcs | 0.0486 \xce\xbcs | 0.0379 \xce\xbcs | 0.0153 | 104乙 |
大批 | 17.547\xce\xbcs | 0.0465 \xce\xbcs | 0.0412 \xce\xbcs | 5.0964 | 32096乙 |
返回 | 31.306 \xce\xbcs | 0.0816 \xce\xbcs | 0.0681 \xce\xbcs | 6.3477 | 40096乙 |
前置 | 26.489 \xce\xbcs | 0.1392 \xce\xbcs | 0.1162 \xce\xbcs | 8.9417 | 56096乙 |
附加 | 26.555 \xce\xbcs | 0.0728 \xce\xbcs | 0.0681 \xce\xbcs | 8.9417 | 56096乙 |
归档时间: |
|
查看次数: |
1302 次 |
最近记录: |