使用C# 11的静态接口方法改进 面向约定 的设计方法

 更新时间:2022年12月12日 09:36:08   作者:Artech  
我们知道接口是针对契约的定义,但是一直以来它只能定义一组“实例”的契约,而不能定义类型的契约,因为定义在接口中的方法只能是实例方,这篇文章主要介绍了使用C# 11的静态接口方法改进面向约定 的设计,需要的朋友可以参考下

C# 11带来了一个我期待已久的特性——接口方法。我们知道接口是针对契约的定义,但是一直以来它只能定义一组“实例”的契约,而不能定义类型(的静态成员)的契约,因为定义在接口中的方法只能是实例方法。由于缺乏针对“类型契约”的支持,我们在设计一些框架或者类库的时候,只能采用“按照约定”的设计,比如ASP.NET Core Minimal API针对参数的绑定就是一个典型的案例。

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

var app = WebApplication.Create();
app.Map("/", (Point point) => point);
app.Run();

public class Point
{
    public double X { get; }
    public double Y { get; }
    public Point(double x, double y)
    {
        X = x;
        Y = y;
    }

    public override string ToString() => $"{X},{Y}";

    public static bool TryParse(string expression, out Point? result)
    {
        result = default;
        var parts = expression.Split(',');
        if (parts.Length != 2) return false;
        if (!double.TryParse(parts[0], out var x) || !double.TryParse(parts[1], out var y)) return false;
        result = new Point(x, y);
        return true;
    }
}

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

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

public class Point
{
    ...
    public static ValueTask<Point?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
    {
        Point? result = default;
        var name = parameter.Name;
        var value = httpContext.GetRouteData().Values.TryGetValue(name!, out var v) ? v : httpContext.Request.Query[name!].SingleOrDefault();
        if (value is string expression && TryParse(expression, out var point))
        {
            result = point;
        }
        return new ValueTask<Point?>(result);
    }
}

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

public interface IBindable<T>
{
    abstract static ValueTask<T?> BindAsync(HttpContext httpContext, ParameterInfo parameter);
}

public interface IParsable<T>
{
    abstract static bool TryParse(string expression, out T? result);
}

public class Point : IBindable<Point>, IParsable<Point>
{
    public double X { get; }
    public double Y { get; }
    public Point(double x, double y)
    {
        X = x;
        Y = y;
    }

    public override string ToString() => $"{X},{Y}";

    public static bool TryParse(string expression, out Point? result)
    {
        result = default;
        var parts = expression.Split(',');
        if (parts.Length != 2) return false;
        if (!double.TryParse(parts[0], out var x) || !double.TryParse(parts[1], out var y)) return false;
        result = new Point(x, y);
        return true;
    }

    public static ValueTask<Point?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
    {
        Point? result = default;
        var name = parameter.Name;
        var value = httpContext.GetRouteData().Values.TryGetValue(name!, out var v) ? v : httpContext.Request.Query[name!].SingleOrDefault();
        if (value is string expression && TryParse(expression, out var point))
        {
            result = point;
        }
        return new ValueTask<Point?>(result);
    }
}

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

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

静态接口设计被应用到基于微服务框架go-micro开发gRPC应用程序中,我在表示gRPC服务的接口中定义了如下的静态方法Bind将本服务类型中定义的gRPC方法绑定成路由。

public interface IGrpcService<TService> where TService : class
{
    static abstract void Bind(IServiceBinder<TService> binder);
}

[GrpcService(ServiceName = "Greeter")]
public class GreeterService: IGrpcService<GreeterService>
{
    public Task<HelloReply> SayHelloUnaryAsync(HelloRequest request, ServerCallContext context);

    public async Task<HelloReply> SayHelloClientStreamingAsync(IAsyncStreamReader<HelloRequest> reader, ServerCallContext context);

    public  async Task SayHelloServerStreamingAsync(Empty request, IServerStreamWriter<HelloReply> responseStream, ServerCallContext context);

    public async Task SayHelloDuplexStreamingAsync(IAsyncStreamReader<HelloRequest> reader, IServerStreamWriter<HelloReply> writer, ServerCallContext context);

    public static void Bind(IServiceBinder<GreeterService> binder)
    {
        binder
            .AddUnaryMethod<HelloRequest, HelloReply>(it =>it.SayHelloUnaryAsync(default!,default!), HelloRequest.Parser)
            .AddClientStreamingMethod<HelloRequest, HelloReply>(it => it.SayHelloClientStreamingAsync(default!, default!), HelloRequest.Parser)
            .AddServerStreamingMethod<Empty, HelloReply>(nameof(SayHelloServerStreamingAsync), it => it.SayHelloServerStreamingAsync, Empty.Parser)
            .AddDuplexStreamingMethod<HelloRequest, HelloReply>(nameof(SayHelloDuplexStreamingAsync), it => it.SayHelloDuplexStreamingAsync, HelloRequest.Parser);
    }

到此这篇关于使用C# 11的静态接口方法改进 面向约定 的设计的文章就介绍到这了,更多相关C# 11的静态接口方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 在Parallel中使用DbSet.Add()发现的一系列多线程问题和解决思路详解

    在Parallel中使用DbSet.Add()发现的一系列多线程问题和解决思路详解

    这篇文章主要介绍了在Parallel中使用DbSet.Add()发现的一系列多线程问题和解决过程的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-11-11
  • Unity创建平铺网格地图的方法

    Unity创建平铺网格地图的方法

    这篇文章主要为大家详细介绍了Unity创建平铺网格地图的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • C#创建一个可快速重复使用的项目模板(详细过程)

    C#创建一个可快速重复使用的项目模板(详细过程)

    这篇文章主要介绍了C#如何创建一个可快速重复使用的项目模板今天给大家介绍的是基于官方的cli donet new 命令创建自己的项目模板,需要的朋友可以参考下
    2024-06-06
  • 详解LINQ入门(上篇)

    详解LINQ入门(上篇)

    这篇文章主要介绍了详解LINQ入门(上篇),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • 浅析C# Dynamic关键字

    浅析C# Dynamic关键字

    这篇文章主要介绍了C# Dynamic关键字的相关资料,文中讲解非常细致,对大家学习C# Dynamic关键字有所帮助,感兴趣的朋友可以了解下
    2020-08-08
  • C#组合函数的使用详解

    C#组合函数的使用详解

    本篇文章是对C#中的组合函数的使用进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • C# XML基础入门小结(XML文件内容增删改查清)

    C# XML基础入门小结(XML文件内容增删改查清)

    本文主要介绍了C# XML基础入门小结(XML文件内容增删改查清),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • c#使用正则表达式匹配字符串验证URL示例

    c#使用正则表达式匹配字符串验证URL示例

    这篇文章主要介绍了c#使用正则表达式的小示例,匹配字符串、验证URL,大家参考使用吧
    2013-12-12
  • C#使用Task.ContinueWith组合任务

    C#使用Task.ContinueWith组合任务

    这篇文章介绍了C#使用Task.ContinueWith组合任务的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-04-04
  • C#实现给DevExpress中GridView表格指定列添加进度条

    C#实现给DevExpress中GridView表格指定列添加进度条

    这篇文章主要为大家详细介绍了如何利用C#实现给DevExpress中GridView表格指定列添加进度条显示效果,感兴趣的小伙伴可以尝试一下
    2022-06-06

最新评论