假设你有一个字符串列表(或任何其他类型,仅以字符串为例(,例如
IEnumerable<string> fullList = ...;
和一个异步谓词,例如
static Task<bool> IncludeString(string s) { ... }
按该谓词筛选列表的最简单方法是什么,具有以下约束:
- 谓词
- 不应按顺序运行(假设列表很长,异步谓词很慢(
- 生成的筛选列表应保留顺序
我确实找到了一个解决方案,但它涉及创建一个临时列表,其中包含每个条目的谓词结果,然后使用它进行过滤。只是感觉不够优雅。在这里:
var includedIndices = await Task.WhenAll(fullList.Select(IncludeString));
var filteredList = fullList.Where((_, i) => includedIndices[i]);
感觉就像一个简单的框架调用应该可以实现的事情,但我找不到。
它不是特别优雅,但您可以在 select 中的谓词Task.ContinueWith
调用中创建匿名类型,等待对该数组的WhenAll
调用,并使用这些任务结果中包含的值。
public async Task<T[]> FilterAsync<T>(IEnumerable<T> sourceEnumerable, Func<T, Task<bool>> predicateAsync)
{
return (await Task.WhenAll(
sourceEnumerable.Select(
v => predicateAsync(v)
.ContinueWith(task => new { Predicate = task.Result, Value = v })))
).Where(a => a.Predicate).Select(a => a.Value).ToArray();
}
示例用法(用于演示的虚构功能(:
// Returns { "ab", "abcd" } after 1000ms
string[] evenLengthStrings = await FilterAsync<string>(new string[] { "a", "ab", "abc", "abcd" }, (async s => { await Task.Delay(1000); return s.Length % 2 == 0; }));
请注意,即使没有 ToArray
调用,返回的 enumerable 也不会在枚举时重新枚举源 enumerable - 它不会是惰性的,因为Task.WhenAll
不会返回 LINQy 惰性枚举。
您可以创建自己所需的Linq
函数的实现,即
public static async Task<IEnumerable<TIn>> FilterAsync<TIn>(this IEnumerable<TIn> source, Func<TIn, Task<bool>> action)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (action == null) throw new ArgumentNullException(nameof(action));
var result = new List<TIn>();
foreach (var item in source)
{
if (await action(item))
{
result.Add(item);
}
}
return result;
}
然后你可以这样使用它
IEnumerable<string> example = new List<string> { "a", "", null, " ", "e" };
var validStrings = await example.FilterAsync(IncludeString);
// returns { "a", "e" }
鉴于这种IncludeString
的实现
public static Task<bool> IncludeString(string s) {
return Task.FromResult(!string.IsNullOrWhiteSpace(s));
}
所以它基本上为列表中的每个项目运行一个async Func<int, Task<bool>>