将文档放到代码里面,文档才会及时地更新!

微软从 .NET Framework 4.0 开始,增加了 System.Diagnostics.Contracts 命名空间,用来把契约文档融入代码。然而后面一直不冷不热,Visual Studio 都没天然支持。ReSharper 不知何时加入了 ReSharper Annotations,在 ReSharper 插件工作的情况下能够进行静态契约的验证。C#8.0 的可空引用类型是 Roslyn 对 null 的验证,本以为会带来编译级别的警告,没想到也只是契约。


 

契约式编程

当你调用某个类库里面的方法时,你如何能够知道传入的参数是否符合规范?如何能够知道方法调用结束之后是否要对结果进行判断?

T DoSomething<T>(T parent) where T : class;

▲ 对于上面的方法,你知道 null 传入参数是合理的吗?返回的参数需要判空吗?

代码的编写者可能是这么写的:

public T DoSomething<T>(T parent) where T : class
{
if (parent == null)
{
throw new ArgumentNullException(nameof(parent));
}
// 后续逻辑。
}

有些静态代码检查工具也许可以根据这里的参数判断代码块来认定为此处的参数不能为 null,但这种判断代码无处不在,静态检查工具如何能够有效地捕获每一处的检查呢?难道我们真的要去翻阅文档吗?然而除非是专门提供 SDK 的团队,否则文档通常都会滞后于代码,那么对于这些契约的修改可能就不太准确。

于是,契约式编程就应运而生。

它将前置条件(Precondition)、后置条件(Postcondition)、不变量(Invariant)等代码分离出来,按照特定的格式编写以便能够被静态检查工具分析出来。

有了静态分析工具以及契约代码的帮助,Visual Studio 的智能感知提示将能够直接告诉我们代码编写的潜在问题,而不必等到运行时再抛出异常,那时将降低开发效率,将增加生产环境运行的风险。

几种不同的契约方法

ReSharper Annotations

ReSharper 并没有将其称之为“契约”,因为它真的只是“文档级别”的约束,只会在写代码的时候具备一定程度的静态分析能力以便给出提示,并不提供运行时的检查。不过,ReSharper 会为我们生成运行时检查的代码。只要是装了 ReSharper 插件并用它写过代码的,应该都见过 ReSharper Annotations 了,因为它会在我们试图添加契约代码时自动添加契约标记(Attribute)。


▲ 生成 ReSharper Annotations

如果错过了首次提示,可以在 ReSharper 的设置界面中生成 Annotations 的代码。(复制一份代码然后新建一个文件粘贴。)


▲ 手动生成 ReSharper Annotations

ReSharper 中常用的契约 Attribute

  • CanBeNull

    • 表示参数或返回值可能为 null。
  • CannotApplyEqualityOperator
    • 表示某个类型的相等比较不应该用 == 或 !=,而应该用 Equals。
  • ItemCanBeNull
    • 表示集合参数或集合返回值里某一项可能为 null。
  • ItemNotNull
    • 表示集合参数或集合返回值里每一项都不为 null。
  • LinqTunnel
    • 表示某个方法就像 linq 方法一样。
  • LocalizationRequired
    • 表示参数字符串需要被本地化。
  • NotNull
    • 表示参数或返回值不可能为 null。
  • PathReference
    • 表示参数字符串是一个路径。
  • Pure
    • 表示方法不会修改任何状态(这意味着如果连返回值都不用,那调用了也相当于什么都没做)。
  • RegexPattern
    • 表示参数字符串是一个正则表达式(会被 ReSharper 代码着色)。
  • 还有 100+ 个……

  • ContractAnnotation

System.Diagnostics.Contracts

此命名空间下的 Contract 类型定义了几个方法,覆盖了我们编写一个方法所要遵循的契约模式。

private T DoSomething<T>(T parent) where T : class
{
// * 要开始此任务必须先满足某些条件(Requires,RequiresAlways,EndContractBlock)
// 做一些操作。
// * 此时认定一定满足某个条件(Assume)
// 继续执行一些操作。
// * 操作执行完后一定满足某组条件(Ensures,EnsuresOnThrows)
}

以上代码中,型号(*)表示契约代码,其他表示方法内的普通代码。一个典型的例子如以下代码所示:

private T DoSomething<T>(T parent) where T : class
{
// * 要开始此任务必须先满足某些条件(Requires,EndContractBlock)
Contract.Requires<ArgumentNullException>(parent != null); // 做一些操作。 // * 此时认定一定满足某个条件(Assume)
Contract.Assume(parent != null); // 继续执行一些操作。 // * 操作执行完后一定满足某组条件(Ensures,EnsuresOnThrows)
Contract.EnsuresOnThrow<InvalidOperationException>(Value != null);
}

在这里,Requires 是真的会抛出异常的,但 AssumeEnsuresOnThrow 是需要写条件编译符为 CONTRACTS_FULL 的。

或者,这样用普通的抛异常的方式。

private T DoSomething<T>(T parent) where T : class
{
// * 要开始此任务必须先满足某些条件(Requires,EndContractBlock)
if (parent == null) throw new ArgumentNullException(nameof(parent));
Contract.EndContractBlock(); // 做一些操作。 // * 此时认定一定满足某个条件(Assume)
Contract.Assume(parent != null); // 继续执行一些操作。 // * 操作执行完后一定满足某组条件(Ensures,EnsuresOnThrows)
Contract.EnsuresOnThrow<InvalidOperationException>(Value != null);
}

对此契约的静态分析微软有提供工具:Microsoft/CodeContracts: Source code for the CodeContracts tools for .NET,ReSharper 对此也有一丁点儿的支持。

Roslyn

Roslyn 相比于任何第三方契约的优势在于它甚至能在语法层面形成契约(比如 C#8.0 中的可空引用类型)。

实际应用

事实上在 GitHub 中,使用各种契约的都有,不过以 ReSharper Annotations 和 System.Diagnostics.Contracts 的居多;C#8.0 的可空引用类型等到 8.0 发布以后再看吧。

在实际应用中,并没有严格的说哪一个更好哪一个一般,两者都可以用,只要我们有分析和提示此契约的工具,就可以在项目中推行开来。

但是,基于契约编写代码的模式却能帮助我们写出更加健壮的代码来。也就是说,用哪个并不重要,重要的是——用起来


参考资料

C#/.NET 中的契约的更多相关文章

  1. .NET 4.0 中的契约式编程

    契约式编程不是一门崭新的编程方法论.C/C++ 时代早已有之.Microsoft 在 .NET 4.0 中正式引入契约式编程库.博主以为契约式编程是一种相当不错的编程思想,每一个开发人员都应该掌握.它 ...

  2. WCF中数据契约之已知类型的几种公开方式

    WCF中传输的数据不想传统的面向对象编程,它只传递了一些对象的属性,但是自身并不知道自己属于什么对象,所以,他没有子类和父类的概念,因而也就没有Is-a的关系,所以在WCF中,如果想维持这种继承关系, ...

  3. (3)MEF插件系统中通信机制的设计和实现

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 一般的WinForm中通过C#自带的Event机制便能很好的实 ...

  4. 在C#中,Json的序列化和反序列化的几种方式总结

    在这篇文章中,我们将会学到如何使用C#,来序列化对象成为Json格式的数据,以及如何反序列化Json数据到对象. 什么是JSON? JSON (JavaScript Object Notation) ...

  5. [WCF编程]4.契约概述

    一.契约的基本概念 契约是消息参与者之间的约定.在SOA架构中,契约提供了服务通信所必需的元数据.契约用来定义数据类型,操作,消息交换模式和消息交换使用的传输协议.契约通常是在标准化平台中使用与编程语 ...

  6. 跟我一起学WCF(6)——深入解析服务契约[下篇]

    一.引言 在上一篇博文中,我们分析了如何在WCF中实现操作重载,其主要实现要点是服务端通过ServiceContract的Name属性来为操作定义一个别名来使操作名不一样,而在客户端是通过重写客户端代 ...

  7. 跟我一起学WCF(5)——深入解析服务契约[上篇]

    一.引言 在上一篇博文中,我们创建了一个简单WCF应用程序,在其中介绍到WCF最重要的概念又是终结点,而终结点又是由ABC组成的.对于Address地址也就是告诉客户端WCF服务所在的位置,而Cont ...

  8. CsharpThinking---代码契约CodeContract(八)

    代码契约(Code Contract):它并不是语言本身的新功能,而是一些额外的工具,帮助人们控制代码边界. 代码契约之于C#,就相当于诗词歌赋之于语言. --- C# in Depth 一,概述 1 ...

  9. 核心概念 —— 契约(Contracts)

    1.简介 Laravel中的契约是指框架提供的一系列定义核心服务的接口. 例如 ,Illuminate\Contracts\Queue\Queue契约定义了队列任务需要实现的方法,Illuminate ...

随机推荐

  1. hdu 4602 Partition 矩阵快速幂

    Partition Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Proble ...

  2. LeetCode第[78]题(Java):Subsets(求子集)扩展——第[90]题:Subsets 2

    题目:矩阵置0 难度:Easy 题目内容:   Given a set of distinct integers, nums, return all possible subsets (the pow ...

  3. Eclipse 中从SVN下载的项目如何解除关联上传到另一SVN路径下

    首先选中项目右击-->team-->断开连接  选择第一个即可断开与上一个SVN关联 然后上传到另一SVN路径下 选中项目右击-->team-->Share Project  ...

  4. 面向目标的场景设置--Goal-Oriented Scenario

    在场景设置的时候会有两种场景设置方式: 1,手动模式(Manual Scenario) 2.面向目标的场景设置模式(Goal Oriented scenario) 其中手动模式使用较多,而且灵活应用, ...

  5. [转]基于Visual Studio 2010 进行敏捷/Scrum模式开发

    http://www.infoq.com/cn/articles/visual-studio-2010-agile-scrum-development 根据Forrester Research今年第二 ...

  6. html表格导出Excel的一点经验心得(转载)

    最近在做统计功能,要求统计结果(表格)既能查看(BS系统,在浏览器查看),又能输出为excel文件.对于输出excel文件,在网上找到n种方案,因为还需查看,最终选择了统计结果输出为table,查看时 ...

  7. SSM整合AOP,日志框架和拦截器

    前言 日志是所有系统必不可少的部分,而AOP在MVC通常用于监控方法调用,可以生成一个traceid,记录从用户调用到底层数据库的数据链路,帮助监控和排查问题. AOP 现在做一个简单的前置切面,用来 ...

  8. 高射炮打蚊子丨在VS 2017里用C语言写经典的冒泡排序

    ​上一期<高射炮打蚊子丨用Visual Studio 2017写最初级的C语言程序>中,我们用Visual Studio “全宇宙最强IDE”这门大炮,打了“C语言写Hello World ...

  9. Django知识点总结

    一.什么是web框架? 框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以帮你快速开发特定的系统,简单地说,就是你用别人搭建好的舞台来做表演. web应 ...

  10. XML 测验

    测试地址http://www.w3school.com.cn/quiz/quiz.asp?quiz=xml 1.XML指的是? 您的回答:eXtensible Markup Language 2.XM ...