在 C# 中,如何提取生成 return 的代码块?
In C#, how can I extract a block of code which yields the return?
我有几个方法都结束了:
while (await cursor.MoveNextAsync())
{
foreach (FieldServiceAppointment appointment in cursor.Current)
{
yield return appointment;
}
}
例如:
public async IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
using IAsyncCursor<FieldServiceAppointment> cursor = await FieldServiceAppointments.FindAsync(
x => x.BusinessEventTypeCode == workOrderType
&& x.CustomerCode == customerId
);
while (await cursor.MoveNextAsync())
{
foreach (FieldServiceAppointment appointment in cursor.Current)
{
yield return appointment;
}
}
}
我想删除这个重复项。
如果我尝试将其重构为:
public async IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
using IAsyncCursor<FieldServiceAppointment> cursor = await FieldServiceAppointments.FindAsync(
x => x.BusinessEventTypeCode == workOrderType
&& x.CustomerCode == customerId
);
YieldAppointments(cursor);
}
public async IAsyncEnumerable<FieldServiceAppointment> YieldAppointments(
IAsyncCursor<FieldServiceAppointment> cursor)
{
while (await cursor.MoveNextAsync())
{
foreach (FieldServiceAppointment appointment in cursor.Current)
{
yield return appointment;
}
}
}
它不会编译,因为我不能 return 来自迭代器的值。
如果我尝试 return yield return YieldAppointments(cursor);
,它不会编译,因为:
Severity Code Description Project File Line Suppression State
Error CS0266 Cannot implicitly convert type
'System.Collections.Generic.IAsyncEnumerable<DataAccessLayer.Entities.Praxedo.FieldServiceAppointment>'
to 'DataAccessLayer.Entities.Praxedo.FieldServiceAppointment'. An
explicit conversion exists (are you missing a
cast?) DataAccessLayer C:\projects\EnpalPraxedoIntegration\DataAccessLayer\DbServices\FieldServiceAutomationDbService.cs 78 Active
所以我尝试
yield return (IAsyncEnumerable<FieldServiceAppointment>) YieldAppointments(cursor);
和
yield return YieldAppointments(cursor) as IAsyncEnumerable <FieldServiceAppointment>;
其中任何一个都会产生以下编译器错误:
Severity Code Description Project File Line Suppression State
Error CS0266 Cannot implicitly convert type
'System.Collections.Generic.IAsyncEnumerable<DataAccessLayer.Entities.Praxedo.FieldServiceAppointment>'
to 'DataAccessLayer.Entities.Praxedo.FieldServiceAppointment'. An
explicit conversion exists (are you missing a
cast?) DataAccessLayer C:\projects\EnpalPraxedoIntegration\DataAccessLayer\DbServices\FieldServiceAutomationDbService.cs 78 Active
然后我尝试了:
public async IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
using IAsyncCursor<FieldServiceAppointment> cursor = await FieldServiceAppointments.FindAsync(
x => x.BusinessEventTypeCode == workOrderType
&& x.CustomerCode == customerId
);
yield return await YieldAppointments(cursor);
}
public async Task<FieldServiceAppointment> YieldAppointments(
IAsyncCursor<FieldServiceAppointment> cursor)
{
while (await cursor.MoveNextAsync())
{
foreach (FieldServiceAppointment appointment in cursor.Current)
{
yield return appointment;
}
}
}
但这不会编译因为
Severity Code Description Project File Line Suppression State
Error CS1624 The body of
'FieldServiceAutomationDbService.YieldAppointments(IAsyncCursor)'
cannot be an iterator block because 'Task' is
not an iterator interface
type DataAccessLayer C:\projects\EnpalPraxedoIntegration\DataAccessLayer\DbServices\FieldServiceAutomationDbService.cs 81 Active
有没有办法让它工作?
用一个 Task
参数化 YieldAppointments
给你一个光标怎么样?
public async IAsyncEnumerable<FieldServiceAppointment> YieldAppointments(Func<Task<IAsyncCursor<FieldServiceAppointment>>> cursorTask)
{
using var cursor = await cursorTask();
while (await cursor.MoveNextAsync())
{
foreach (FieldServiceAppointment appointment in cursor.Current)
{
yield return appointment;
}
}
}
现在您可以在 lambda 中编写 GetEventWithWorkOrderType
的第一部分(获取光标的位置,以及您可能执行的任何其他异步操作):
public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(string workOrderType, string customerId)
=> YieldAppointments(async () =>
await FieldServiceAppointments.FindAsync(
x => x.BusinessEventTypeCode == workOrderType
&& x.CustomerCode == customerId
);
);
解决此问题的一种方法是使用 Tom Gringauz 的 AsyncEnumerableEx.Using
method from the System.Interactive.Async package, and the ToAsyncEnumerable
extension method from 。
下面是AsyncEnumerableEx.Using
方法的签名:
public static IAsyncEnumerable<TSource> Using<TSource, TResource>(
Func<Task<TResource>> resourceFactory,
Func<TResource, ValueTask<IAsyncEnumerable<TSource>>> enumerableFactory)
where TResource : IDisposable;
下面是 ToAsyncEnumerable
扩展方法,从上述答案复制粘贴,增强了取消支持:
public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(
this IAsyncCursor<T> asyncCursor,
[EnumeratorCancellation]CancellationToken cancellationToken = default)
{
while (await asyncCursor.MoveNextAsync(cancellationToken).ConfigureAwait(false))
foreach (var current in asyncCursor.Current)
yield return current;
}
以下是如何结合这两种方法来解决您的问题:
public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
return AsyncEnumerableEx.Using(() => FieldServiceAppointments
.FindAsync(x => x.BusinessEventTypeCode == workOrderType &&
x.CustomerCode == customerId),
cursor => new ValueTask<IAsyncEnumerable<FieldServiceAppointment>>(cursor.ToAsyncEnumerable()));
}
AsyncEnumerableEx.Using
有一个过于灵活的 enumerableFactory
参数,(在您的情况下)需要将 cursor.ToAsyncEnumerable()
调用包装在冗长且嘈杂的 ValueTask
中。您可以通过将 AsyncEnumerableEx.Using
包装在您自己的 Using
方法中来摆脱这种烦恼:
public static IAsyncEnumerable<TSource> Using<TSource, TResource>(
Func<Task<TResource>> resourceFactory,
Func<TResource, IAsyncEnumerable<TSource>> enumerableFactory)
where TResource : IDisposable
{
return AsyncEnumerableEx.Using(resourceFactory,
resource => new ValueTask<IAsyncEnumerable<TSource>>(
enumerableFactory(resource)));
}
使用此方法,解决方案变得更简单且更具可读性:
public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
return Using(() => FieldServiceAppointments
.FindAsync(x => x.BusinessEventTypeCode == workOrderType &&
x.CustomerCode == customerId),
cursor => cursor.ToAsyncEnumerable());
}
备选方案: 下面是一个自给自足且不那么抽象的备选方案。 FromAsyncCursor
泛型方法采用 asyncCursorFactory
和 returns 包含游标发出的文档的序列:
public async static IAsyncEnumerable<TDocument> FromAsyncCursor<TDocument>(
Func<Task<IAsyncCursor<TDocument>>> asyncCursorFactory,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
using var asyncCursor = await asyncCursorFactory();
while (await asyncCursor.MoveNextAsync(cancellationToken).ConfigureAwait(false))
foreach (var current in asyncCursor.Current)
yield return current;
}
用法示例:
public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
return FromAsyncCursor(() => FieldServiceAppointments.FindAsync(
x => x.BusinessEventTypeCode == workOrderType && x.CustomerCode == customerId));
}
EnumeratorCancellation
属性可以使用生成的序列,同时观察 CancellationToken
:
var cts = new CancellationTokenSource();
await foreach (var item in GetEventWithWorkOrderType("xxx", "yyy")
.WithCancellation(cts.Token))
{
//...
}
虽然 GetEventWithWorkOrderType
方法本身不接受 CancellationToken
,但由于此属性,令牌会神奇地传播到 FromAsyncCursor
方法。不过,所有这些都可能有点学术性,因为单个 MoveNextAsync
不太可能花费这么多时间来获取下一批文档,从而使添加取消支持成为一个令人信服的提议。
我有几个方法都结束了:
while (await cursor.MoveNextAsync())
{
foreach (FieldServiceAppointment appointment in cursor.Current)
{
yield return appointment;
}
}
例如:
public async IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
using IAsyncCursor<FieldServiceAppointment> cursor = await FieldServiceAppointments.FindAsync(
x => x.BusinessEventTypeCode == workOrderType
&& x.CustomerCode == customerId
);
while (await cursor.MoveNextAsync())
{
foreach (FieldServiceAppointment appointment in cursor.Current)
{
yield return appointment;
}
}
}
我想删除这个重复项。
如果我尝试将其重构为:
public async IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
using IAsyncCursor<FieldServiceAppointment> cursor = await FieldServiceAppointments.FindAsync(
x => x.BusinessEventTypeCode == workOrderType
&& x.CustomerCode == customerId
);
YieldAppointments(cursor);
}
public async IAsyncEnumerable<FieldServiceAppointment> YieldAppointments(
IAsyncCursor<FieldServiceAppointment> cursor)
{
while (await cursor.MoveNextAsync())
{
foreach (FieldServiceAppointment appointment in cursor.Current)
{
yield return appointment;
}
}
}
它不会编译,因为我不能 return 来自迭代器的值。
如果我尝试 return yield return YieldAppointments(cursor);
,它不会编译,因为:
Severity Code Description Project File Line Suppression State Error CS0266 Cannot implicitly convert type 'System.Collections.Generic.IAsyncEnumerable<DataAccessLayer.Entities.Praxedo.FieldServiceAppointment>' to 'DataAccessLayer.Entities.Praxedo.FieldServiceAppointment'. An explicit conversion exists (are you missing a cast?) DataAccessLayer C:\projects\EnpalPraxedoIntegration\DataAccessLayer\DbServices\FieldServiceAutomationDbService.cs 78 Active
所以我尝试
yield return (IAsyncEnumerable<FieldServiceAppointment>) YieldAppointments(cursor);
和
yield return YieldAppointments(cursor) as IAsyncEnumerable <FieldServiceAppointment>;
其中任何一个都会产生以下编译器错误:
Severity Code Description Project File Line Suppression State Error CS0266 Cannot implicitly convert type 'System.Collections.Generic.IAsyncEnumerable<DataAccessLayer.Entities.Praxedo.FieldServiceAppointment>' to 'DataAccessLayer.Entities.Praxedo.FieldServiceAppointment'. An explicit conversion exists (are you missing a cast?) DataAccessLayer C:\projects\EnpalPraxedoIntegration\DataAccessLayer\DbServices\FieldServiceAutomationDbService.cs 78 Active
然后我尝试了:
public async IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
using IAsyncCursor<FieldServiceAppointment> cursor = await FieldServiceAppointments.FindAsync(
x => x.BusinessEventTypeCode == workOrderType
&& x.CustomerCode == customerId
);
yield return await YieldAppointments(cursor);
}
public async Task<FieldServiceAppointment> YieldAppointments(
IAsyncCursor<FieldServiceAppointment> cursor)
{
while (await cursor.MoveNextAsync())
{
foreach (FieldServiceAppointment appointment in cursor.Current)
{
yield return appointment;
}
}
}
但这不会编译因为
Severity Code Description Project File Line Suppression State Error CS1624 The body of 'FieldServiceAutomationDbService.YieldAppointments(IAsyncCursor)' cannot be an iterator block because 'Task' is not an iterator interface type DataAccessLayer C:\projects\EnpalPraxedoIntegration\DataAccessLayer\DbServices\FieldServiceAutomationDbService.cs 81 Active
有没有办法让它工作?
用一个 Task
参数化 YieldAppointments
给你一个光标怎么样?
public async IAsyncEnumerable<FieldServiceAppointment> YieldAppointments(Func<Task<IAsyncCursor<FieldServiceAppointment>>> cursorTask)
{
using var cursor = await cursorTask();
while (await cursor.MoveNextAsync())
{
foreach (FieldServiceAppointment appointment in cursor.Current)
{
yield return appointment;
}
}
}
现在您可以在 lambda 中编写 GetEventWithWorkOrderType
的第一部分(获取光标的位置,以及您可能执行的任何其他异步操作):
public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(string workOrderType, string customerId)
=> YieldAppointments(async () =>
await FieldServiceAppointments.FindAsync(
x => x.BusinessEventTypeCode == workOrderType
&& x.CustomerCode == customerId
);
);
解决此问题的一种方法是使用 Tom Gringauz 的 AsyncEnumerableEx.Using
method from the System.Interactive.Async package, and the ToAsyncEnumerable
extension method from
下面是AsyncEnumerableEx.Using
方法的签名:
public static IAsyncEnumerable<TSource> Using<TSource, TResource>(
Func<Task<TResource>> resourceFactory,
Func<TResource, ValueTask<IAsyncEnumerable<TSource>>> enumerableFactory)
where TResource : IDisposable;
下面是 ToAsyncEnumerable
扩展方法,从上述答案复制粘贴,增强了取消支持:
public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(
this IAsyncCursor<T> asyncCursor,
[EnumeratorCancellation]CancellationToken cancellationToken = default)
{
while (await asyncCursor.MoveNextAsync(cancellationToken).ConfigureAwait(false))
foreach (var current in asyncCursor.Current)
yield return current;
}
以下是如何结合这两种方法来解决您的问题:
public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
return AsyncEnumerableEx.Using(() => FieldServiceAppointments
.FindAsync(x => x.BusinessEventTypeCode == workOrderType &&
x.CustomerCode == customerId),
cursor => new ValueTask<IAsyncEnumerable<FieldServiceAppointment>>(cursor.ToAsyncEnumerable()));
}
AsyncEnumerableEx.Using
有一个过于灵活的 enumerableFactory
参数,(在您的情况下)需要将 cursor.ToAsyncEnumerable()
调用包装在冗长且嘈杂的 ValueTask
中。您可以通过将 AsyncEnumerableEx.Using
包装在您自己的 Using
方法中来摆脱这种烦恼:
public static IAsyncEnumerable<TSource> Using<TSource, TResource>(
Func<Task<TResource>> resourceFactory,
Func<TResource, IAsyncEnumerable<TSource>> enumerableFactory)
where TResource : IDisposable
{
return AsyncEnumerableEx.Using(resourceFactory,
resource => new ValueTask<IAsyncEnumerable<TSource>>(
enumerableFactory(resource)));
}
使用此方法,解决方案变得更简单且更具可读性:
public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
return Using(() => FieldServiceAppointments
.FindAsync(x => x.BusinessEventTypeCode == workOrderType &&
x.CustomerCode == customerId),
cursor => cursor.ToAsyncEnumerable());
}
备选方案: 下面是一个自给自足且不那么抽象的备选方案。 FromAsyncCursor
泛型方法采用 asyncCursorFactory
和 returns 包含游标发出的文档的序列:
public async static IAsyncEnumerable<TDocument> FromAsyncCursor<TDocument>(
Func<Task<IAsyncCursor<TDocument>>> asyncCursorFactory,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
using var asyncCursor = await asyncCursorFactory();
while (await asyncCursor.MoveNextAsync(cancellationToken).ConfigureAwait(false))
foreach (var current in asyncCursor.Current)
yield return current;
}
用法示例:
public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
string workOrderType, string customerId)
{
return FromAsyncCursor(() => FieldServiceAppointments.FindAsync(
x => x.BusinessEventTypeCode == workOrderType && x.CustomerCode == customerId));
}
EnumeratorCancellation
属性可以使用生成的序列,同时观察 CancellationToken
:
var cts = new CancellationTokenSource();
await foreach (var item in GetEventWithWorkOrderType("xxx", "yyy")
.WithCancellation(cts.Token))
{
//...
}
虽然 GetEventWithWorkOrderType
方法本身不接受 CancellationToken
,但由于此属性,令牌会神奇地传播到 FromAsyncCursor
方法。不过,所有这些都可能有点学术性,因为单个 MoveNextAsync
不太可能花费这么多时间来获取下一批文档,从而使添加取消支持成为一个令人信服的提议。