使用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的静态接口方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • [C#].NET中几种Timer的使用实例

    [C#].NET中几种Timer的使用实例

    本篇文章主要介绍了.NET中几种Timer的使用,具有一定的参考价值,有兴趣的可以了解一下。
    2016-12-12
  • winform 调用摄像头扫码识别二维码的实现步骤

    winform 调用摄像头扫码识别二维码的实现步骤

    这篇文章主要介绍了winform 调用摄像头扫码识别二维码的实现步骤,帮助大家更好的理解和学习使用winform,感兴趣的朋友可以了解下
    2021-02-02
  • C# Winform实现石头剪刀布游戏

    C# Winform实现石头剪刀布游戏

    这篇文章主要为大家详细介绍了Winform实现石头剪刀布游戏,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • 浅谈C#中对引用类型的误解

    浅谈C#中对引用类型的误解

    这篇文章主要介绍了浅谈C#中对引用类型的误解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • C# 根据字符串生成二维码的实例代码

    C# 根据字符串生成二维码的实例代码

    这篇文章主要介绍了C# 根据字符串生成二维码的实例,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • Js中的substring,substr与C#中的Substring比较

    Js中的substring,substr与C#中的Substring比较

    本篇文章主要是对Js中的substring,substr与C#中的Substring进行了比较。需要的朋友可以过来参考下,希望对大家有所帮助
    2014-01-01
  • 基于mvc5+ef6+Bootstrap框架实现身份验证和权限管理

    基于mvc5+ef6+Bootstrap框架实现身份验证和权限管理

    最近刚做完一个项目,项目架构师使用mvc5+ef6+Bootstrap,用的是vs2015,数据库是sql server2014。下面小编把mvc5+ef6+Bootstrap项目心得之身份验证和权限管理模块的实现思路分享给大家,需要的朋友可以参考下
    2016-06-06
  • 详解C#中SqlParameter的作用与用法

    详解C#中SqlParameter的作用与用法

    本篇文章主要介绍了C#中SqlParameter的作用与用法,因为通过SQL语句的方式,有时候存在脚本注入的危险,所以在大多数情况下不建议用拼接SQL语句字符串方式,希望通过SqlParameter实现来实现对数据的操作。
    2016-12-12
  • C# 操作网络适配器的示例

    C# 操作网络适配器的示例

    这篇文章主要介绍了C# 操作网络适配器的示例,帮助大家更好的理解和学习c#,感兴趣的朋友可以了解下
    2020-10-10
  • 关于C#版Nebula客户端编译的问题

    关于C#版Nebula客户端编译的问题

    这篇文章主要介绍了C#版Nebula客户端编译的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-07-07

最新评论