我这几天使用EF Core开发网站的时候,突然想试着开发自己的Linq扩展表达式,因为EF Core对IQueryable泛型类型,有很多异步表达式的支持。比如,在项目中只要添加了对EF Core命名空间的using支持,那么IQueryable泛型类型就可以使用FirstAsync等函数。
于是我先查看一下IQueryable类型的Where函数结构,发现该Where函数是因为基于IEnumerable类型才可以通过System.Linq命名空间添加对Where函数的支持。
IEnumerable类型的Where函数结构为如下,通过foreach实现对自身中所有元素的遍历,并同yield关键字在循环中实现IEnumerable的泛型对象返回值:
//提示:该函数结构会一直变化
private static IEnumerable WhereIterator(IEnumerable source, Func predicate)
{
int index = -1;
foreach (TSource element in source)
{
checked
{
index++;
}
if (predicate(element, index))
{
yield return element;
}
}
}
而IAsyncEnumerable泛型类型中,有一个ForEachAsync可以供我们参考
private static async Task ForEachAsync_(IAsyncEnumerable source, Action action, CancellationToken cancellationToken)
{
var index = 0;
using (var e = source.GetEnumerator())
{
while (await e.MoveNext(cancellationToken)
.ConfigureAwait(false))
{
action(e.Current, checked(index++));
}
}
}
可以看到IAsyncEnumerable泛型类型通过MoveNext实现对自身中所有元素的遍历。
while (await e.MoveNext(cancellationToken)
.ConfigureAwait(false))
我们来实现结合以上两个函数的IAsyncEnumerable泛型类型的初步WhereAsync表达式函数
public async static Task> WhereAsync(
this IAsyncEnumerable source,
Func predicate,
CancellationToken cancellationToken = default)
{
using (var e = source.GetEnumerator())
{
while (await e.MoveNext(cancellationToken)
.ConfigureAwait(false))
{
if (predicate(e.Current))
{
yield return e.Current;
}
}
}
}
但是因为当前异步Task没有对yield关键字添加支持,所以会报错,于是我们来将这个函数进行改进一下,完整的代码如下。
public async static Task> WhereAsync(
this IAsyncEnumerable source,
Func predicate,
CancellationToken cancellationToken = default)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
List List = new List();
using (var e = source.GetEnumerator())
{
while (await e.MoveNext(cancellationToken)
.ConfigureAwait(false))
{
if (predicate(e.Current))
{
List.Add(e.Current);
}
}
}
return List.ToAsyncEnumerable();
}
我们改动一下这个函数,使其变为对IEnumerable泛型类型的WhereAsync,完整代码如下
public async static Task> WhereAsync(
this IEnumerable source,
Func predicate,
CancellationToken cancellationToken = default)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
List List = new List();
using (var e = source.ToAsyncEnumerable().GetEnumerator())
{
while (await e.MoveNext(cancellationToken)
.ConfigureAwait(false))
{
if (predicate(e.Current))
{
List.Add(e.Current);
}
}
}
return List;
}
提示大家,当前c#对异步Task表达式还不能很好的支持,形成管道,所以使用中不如同步的表达式比如IEnumerable和List使用起来便捷,开发快速。