经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
使用C# 11的静态接口方法改进 面向约定 的设计
来源:cnblogs  作者:Artech  时间:2022/12/12 9:16:00  对本文有异议

C# 11带来了一个我期待已久的特性——静态接口方法。我们知道接口是针对契约的定义,但是一直以来它只能定义一组“实例”的契约,而不能定义类型(的静态成员)的契约,因为定义在接口中的方法只能是实例方法。由于缺乏针对“类型契约”的支持,我们在设计一些框架或者类库的时候,只能采用“按照约定”的设计,比如ASP.NET Core Minimal API针对参数的绑定就是一个典型的案例。以如下这个简单的应用为例,我们采用Minimal API的形式注册了一个针对根地址“/”的路由,作为处理器的委托的输出和输出都是我们自定义的Point对象。

  1. var app = WebApplication.Create();
  2. app.Map("/", (Point point) => point);
  3. app.Run();
  4.  
  5. public class Point
  6. {
  7. public double X { get; }
  8. public double Y { get; }
  9. public Point(double x, double y)
  10. {
  11. X = x;
  12. Y = y;
  13. }
  14.  
  15. public override string ToString() => $"{X},{Y}";
  16.  
  17. public static bool TryParse(string expression, out Point? result)
  18. {
  19. result = default;
  20. var parts = expression.Split(',');
  21. if (parts.Length != 2) return false;
  22. if (!double.TryParse(parts[0], out var x) || !double.TryParse(parts[1], out var y)) return false;
  23. result = new Point(x, y);
  24. return true;
  25. }
  26. }

Minimal API的约定,如果我们为Point类型定义了具有如上声明的TryParse方法,该方法就会用来帮助我们绑定处理方法的Point参数,如下的演示结果证实了这一点。

image

其实针对参数绑定,我们还可以定义如下这样BindAsync参数来完成。

  1. public class Point
  2. {
  3. ...
  4. public static ValueTask<Point?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
  5. {
  6. Point? result = default;
  7. var name = parameter.Name;
  8. var value = httpContext.GetRouteData().Values.TryGetValue(name!, out var v) ? v : httpContext.Request.Query[name!].SingleOrDefault();
  9. if (value is string expression && TryParse(expression, out var point))
  10. {
  11. result = point;
  12. }
  13. return new ValueTask<Point?>(result);
  14. }
  15. }

对于这种“基于约定”的编程,可以你觉得还不错,但是我想有90%的ASP.NET Core的开发者不知道有这个特性,就从这一点就充分证明了这样的设计还不够好。这样的实现也比较繁琐,我们不得不通过反射检验待绑定参数的类型是否满足约定,并以反射(或者表达式树)的方式调用对应的方法。其实上述两个方法本应该写入“契约”,无奈它们是静态方法,没法定义在接口中。现在我们有了静态接口方法,它们可以定义如下所示的IBindable<T>和IParsable<T>。

  1. public interface IBindable<T>
  2. {
  3. abstract static ValueTask<T?> BindAsync(HttpContext httpContext, ParameterInfo parameter);
  4. }
  5.  
  6. public interface IParsable<T>
  7. {
  8. abstract static bool TryParse(string expression, out T? result);
  9. }
  10.  
  11. public class Point : IBindable<Point>, IParsable<Point>
  12. {
  13. public double X { get; }
  14. public double Y { get; }
  15. public Point(double x, double y)
  16. {
  17. X = x;
  18. Y = y;
  19. }
  20.  
  21. public override string ToString() => $"{X},{Y}";
  22.  
  23. public static bool TryParse(string expression, out Point? result)
  24. {
  25. result = default;
  26. var parts = expression.Split(',');
  27. if (parts.Length != 2) return false;
  28. if (!double.TryParse(parts[0], out var x) || !double.TryParse(parts[1], out var y)) return false;
  29. result = new Point(x, y);
  30. return true;
  31. }
  32.  
  33. public static ValueTask<Point?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
  34. {
  35. Point? result = default;
  36. var name = parameter.Name;
  37. var value = httpContext.GetRouteData().Values.TryGetValue(name!, out var v) ? v : httpContext.Request.Query[name!].SingleOrDefault();
  38. if (value is string expression && TryParse(expression, out var point))
  39. {
  40. result = point;
  41. }
  42. return new ValueTask<Point?>(result);
  43. }
  44. }

实际上IParsable<T>已经存在了,它真正的定义是这样的。如果有了这样的接口,确定带绑定参数类型是否满足之前的约定条件只需要确定其是否实现了对应的接口就可以了。

  1. public interface IParsable<TSelf> where TSelf : IParsable<TSelf>?
  2. {
  3. static TSelf Parse(string s, IFormatProvider? provider);
  4. static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result);
  5. }

静态接口设计被应用到《用最少的代码打造一个Mini版的gRPC框架》中,我在表示gRPC服务的接口中定义了如下的静态方法Bind将本服务类型中定义的gRPC方法绑定成路由。

  1. public interface IGrpcService<TService> where TService : class
  2. {
  3. static abstract void Bind(IServiceBinder<TService> binder);
  4. }
  5.  
  6. [GrpcService(ServiceName = "Greeter")]
  7. public class GreeterService: IGrpcService<GreeterService>
  8. {
  9. public Task<HelloReply> SayHelloUnaryAsync(HelloRequest request, ServerCallContext context);
  10.  
  11. public async Task<HelloReply> SayHelloClientStreamingAsync(IAsyncStreamReader<HelloRequest> reader, ServerCallContext context);
  12.  
  13. public async Task SayHelloServerStreamingAsync(Empty request, IServerStreamWriter<HelloReply> responseStream, ServerCallContext context);
  14.  
  15. public async Task SayHelloDuplexStreamingAsync(IAsyncStreamReader<HelloRequest> reader, IServerStreamWriter<HelloReply> writer, ServerCallContext context);
  16.  
  17. public static void Bind(IServiceBinder<GreeterService> binder)
  18. {
  19. binder
  20. .AddUnaryMethod<HelloRequest, HelloReply>(it =>it.SayHelloUnaryAsync(default!,default!), HelloRequest.Parser)
  21. .AddClientStreamingMethod<HelloRequest, HelloReply>(it => it.SayHelloClientStreamingAsync(default!, default!), HelloRequest.Parser)
  22. .AddServerStreamingMethod<Empty, HelloReply>(nameof(SayHelloServerStreamingAsync), it => it.SayHelloServerStreamingAsync, Empty.Parser)
  23. .AddDuplexStreamingMethod<HelloRequest, HelloReply>(nameof(SayHelloDuplexStreamingAsync), it => it.SayHelloDuplexStreamingAsync, HelloRequest.Parser);
  24. }
  25. }

原文链接:https://www.cnblogs.com/artech/p/static-interface-method.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号