[如果我有一个使用具有许多阶段的管道的应用程序,请在所有阶段上使用foreach
执行并调用:
CanExecute
Execute
接口是这个:
public interface IService
{
bool CanExecute(IContext subject);
IContext Execute(IContext subject);
}
它基本上接受上下文,并返回变得更丰富的上下文。
在阶段Execute
方法之一内,我需要调用服务,并且想要进行异步处理。因此,现在Execute
方法需要更改为例如
Task<IContext> ExecuteAsync(IContext subject);
使用await
拨打服务电话。
所有其他阶段都没有异步代码,但是现在需要更改,因为最佳实践是“一直异步”。
引入异步代码时,进行这些更改是否正常?
C#8提供了避免修改同步服务的多种方法。 C#7也可以使用模式匹配语句来处理此问题。
默认实现成员
接口版本控制是默认接口成员的主要用例之一。它们可以用来避免在接口更改时更改现有的类。您可以为ExecuteAsync
添加默认实现,该实现将Execute
的结果作为ValueTask返回。
假设您具有这些接口:
public interface IContext{}
public interface IService
{
public bool CanExecute(IContext subject);
public IContext Execute(IContext subject);
}
public class ServiceA:IService
{
public bool CanExecute(IContext subject)=>true;
public IContext Execute(IContext subject){return subject;}
}
要创建异步服务无修改同步服务,可以将默认实现添加到IService并在新服务中覆盖它:
public interface IService
{
public bool CanExecute(IContext subject);
public IContext Execute(IContext subject);
public ValueTask<IContext> ExecuteAsync(IContext subject)=>new ValueTask<IContext>(Execute(subject));
}
public class ServiceB:IService
{
public bool CanExecute(IContext subject)=>true;
public IContext Execute(IContext subject)=>ExecuteAsync(subject).Result;
public async ValueTask<IContext> ExecuteAsync(IContext subject)
{
await Task.Yield();
return subject;
}
}
ServiceB.Execute
仍然需要一个主体,并且有意义的一件事是调用ExecuteAsync()
并阻止,看起来很丑。如果调用Execute
,则另一种可能性是抛出:
public IContext Execute(IContext subject)=>throw new InvalidOperationException("This is an async service");
模式匹配
另一个选择是为异步服务创建第二个接口:
将保持不变。管道代码将根据服务的类型进行更改以进行不同的调用:public interface IService { public bool CanExecute(IContext subject); public IContext Execute(IContext subject); } public interface IServiceAsync:IService { public ValueTask<IContext> ExecuteAsync(IContext subject); }
两个服务实现
async Task Main() { IService[] pipeline=new[]{(IService)new ServiceA(),new ServiceB()}; IContext ctx=new Context(); foreach(var svc in pipeline) { if (svc.CanExecute(ctx)) { var result=svc switch { IServiceAsync a=>await a.ExecuteAsync(ctx), IService b => b.Execute(ctx)}; ctx=result; } } }
模式匹配表达式根据当前服务的类型调用不同的分支。在类型上嵌套会产生一个强类型实例(a或b),该实例可用于调用适当的方法。
开关表达式是详尽无遗的-如果编译器无法验证模式是否匹配了所有选项,则将生成警告。
C#7
C#7没有switch表达式,因此需要更详细的模式匹配switch语句:
并不详尽,因此我们需要添加if (svc.CanExecute(ctx)) { switch (svc) { case IServiceAsync a: ctx=await a.ExecuteAsync(ctx); break; case IService b : ctx=b.Execute(ctx); break; default: throw new InvalidOperationException("Unknown service type!"); } }
Switch statements
default
部分以在运行时捕获错误。引入异步代码时,进行这些更改是否正常?