软件约定称为代码约定,通过这一约定可以表示代码正常工作所需的正式条件。 如果方法未按预期收到数据或生成的数据不符合预期的后置条件,代码约定将导致代码引发异常。 有关前置条件和后置条件的概述,您可能需要查看我上个月发表的文章 (msdn.microsoft.com/magazine/gg983479)。

  代码约定是 .NET Framework 4 的一部分,但同样依赖于 Visual Studio 2010 中的一些功能,例如运行时工具、与 MSBuild 集成以及“项目属性”框中的属性页。 值得注意的是,仅编写前置条件和后置条件是不够的。 您还需要为每个项目启用运行时检查功能才能使用软件约定。 您可以通过 Visual Studio 2010 中的“代码约定”项目属性页来完成上述操作。

  在本文中,我将讨论您可以查看或选择的各个选项的预定用途,并深入讨论使用代码约定中的参数验证可以执行的最常见操作的重写程序工具和实践。

  代码约定属性页

  应在所有版本中还是仅在调试版本中实施代码约定前置条件和后置条件? 实际上,这取决于您对软件约定概念的理解。 它是设计工作的一部分吗? 或者,它仅是一种调试措施?

  如果它是设计功能,则没理由剥离发行版中的约定。 如果它仅是一种调试技术,当在发布模式中进行编译时,您不希望显示它。

  在 .NET Framework 中,代码约定仅是此框架的一部分并且未融入任何语言。 这样将更容易在项目中按版本配置它们。 因此,通过软件约定的 .NET Framework 实现,您可以决定实现约定的合适时间和地点。

  图 1 显示 Visual Studio 2010 中的属性页,通过此页可以设置软件约定为应用程序工作的方式。 请注意,此类设置基于项目应用,因此可以根据需要进行调整。

图 1 Visual Studio 2010 中代码约定的属性页

  您可以选择选项配置(调试、发布等)并仅对该配置应用设置。 这样,您可以启用代码约定用于调试但不用于发布,而且更重要的是,您可以随时改变决策。

  运行时检查

  若要启用代码约定,必须选中“执行运行时约定检查”选项。 如果未选中此选项,则在源代码中显示的任何约定说明将可能不会产生任何效果(定义了 DEBUG 符号的任何版本中的 Contract.Assert 和 Contract.Assume 例外,但这不是很重要)。 复选框控制是否在每个编译步骤结束时触发重写程序工具。 重写程序是一个外部工具,用于对软件约定进行后处理并修改 MSIL 代码,以及在合适的位置执行前置条件、后置条件和固定条件检查。

  但是,请注意,如果您具有类似下面这样的前置条件,则在关闭重写程序时会得到运行时断言失败:

Contract.Requires<TException>(condition)

  图 2 显示了您得到的消息框。

图 2 代码需要运行时约定检查

  若要详细查看运行时检查的工作方式,请考虑以下代码:

public Int32 Sum(Int32 x, Int32 y) {
// Check input values
ValidateOperands(x, y);
ValidateResult();

// Perform the operation
if (x == y)
return 2 * x;
return x + y;
}

  约定详细信息使用 ContractAbbreviator 存储在 ValidateXxx 方法中,如上个月的专栏所讨论。 以下是 ValidateXxx 方法的源代码:

[ContractAbbreviator]
private void ValidateOperands(Int32 x, Int32 y) {
Contract.Requires(x >= 0 && y >= 0);
}

[ContractAbbreviator]
private void ValidateResult() {
Contract.Ensures(Contract.Result<Int32>() >= 0);
}

  如果您使用 Contract.Requires 而不是 Contract.Requires<TException>,则在某个版本中关闭重写程序时不会出现图 2 所示的失败。 图 2 中的消息框是由 Contract.Requires 的内部实现所致,如下所示:

[Conditional("CONTRACTS_FULL")]
public static void Requires(bool condition, string userMessage) {
AssertMustUseRewriter(
ContractFailureKind.Precondition, "Requires");
}

public static void Requires<TException>(bool condition)
where TException: Exception {
AssertMustUseRewriter(
ContractFailureKind.Precondition, "Requires<TException>");
}

  条件属性向编译器指示除非定义了 CONTRACTS_FULL 符号,否则应忽略此类方法调用。 仅当您启用“执行运行时约定检查”选项时,才定义此符号。 由于 Contract.Requires<TException> 不是根据条件定义的且缺少该属性,因此将执行重写程序检查,如果禁用运行时约定检查,则会导致失败的断言。

  接下来我们将考虑对上述代码使用重写程序的效果。 您可以方便地亲自验证我所说的,方法是仅使用断点并按 Ctrl+F11 在 Visual Studio 2010 中打开反汇编视图。 图 3 显示了在未启用运行时约定检查的情况下,逐步查看编译的 Sum 方法时反汇编视图的内容。 正如您所看到的,源代码与您在类中编写的代码相同。

图 3 不执行运行时约定检查时的反汇编视图

  如果启用运行时检查,重写程序工具将通过编译器传递,返回并编辑 MSIL 代码。 如果您在启用代码约定的情况下逐步执行相同代码,将看到类似图 4 的内容。

图 4 Return 语句后执行的后置条件检查

  明显的区别是在退出 Sum 方法之前且在 return 语句之后调用 ValidateResult。 您不必是 MSIL 专家就能了解图 4 中所示代码的状况。 在方法开始接受最上面位置的前置条件之前,将对操作数进行验证。 包含后置条件的代码将移动到方法的底部,最后一个 return 语句的 MSIL 代码将也是如此。 更有意思的是,第一个 return 语句(Sum 方法中实现快捷方式的语句)现在只跳到 ValidateResult 开始的地址:

...
return 2 * x;
00000054 mov eax,dword ptr [ebp-8]
00000057 add eax,eax
00000059 mov dword ptr [ebp-0Ch],eax
0000005c nop
0000005d jmp 0000006B
...
ValidateResult();
0000006b push dword ptr ds:[02C32098h]
...

  回到图 1,请注意“执行运行时约定检查”复选框旁边的下拉列表。 您可以通过该列表指示要启用的软件约定数目。 存在多个级别:“完全”、“前置和后置”、“前置条件”、“发行版要求”和“无”。

  “完全”表示支持所有类型的软件约定,“无”表示不考虑任何软件约定。 “前置和后置”排除固定条件。 “前置条件”还排除 Ensure 语句。

  “发行版要求”不支持 Contract.Requires 方法,仅允许使用 Requires<TException> 或旧的 If-Then-Throw 格式指定前置条件。

  通过项目属性页可按项目启用或禁用运行时检查,但是如果您只想对代码的一些部分禁用运行时检查该怎么办? 在这种情况下,只需使用 ContractRuntimeIgnored 属性以编程方式禁用运行时检查。 但是,最新发行版 (1.4.40307.0) 中增加了新的“跳过限定符”选项,该选项也不允许您执行任何包含对 Contract.ForAll 或 Contract.Exists 的引用的约定。

  可以对在 Contract 表达式中使用的成员应用属性。 如果成员已使用此属性加以修饰,则显示该成员的整个 Contract 语句将不会进行运行时检查。 属性不会在 Assert 和 Assume 等 Contract 方法中识别。

  程序集模式

  代码约定属性还可用于为约定配置“程序集模式”设置。 此设置是指您打算执行参数验证的方式。 有两个可能的选项:“标准约定要求”和“约定引用程序集”。 程序集模式设置可帮助重写程序等工具在必要时优化代码并给出合适的警告。 假设您使用程序集模式来声明您使用代码约定进行参数验证的意图。 程序集模式设置引入了一些必须符合的简单规则,否则您将收到编译错误。

  如果您使用 Requires 和 Requires<T> 方法验证参数,程序集模式必须设置为“标准约定要求”。 如果您使用任何 If-Then-Throw 语句作为前置条件,则应使用“自定义参数验证”。 如果您不使用“自定义参数验证”,该语句将被视为 Requires<T>。 自定义参数验证的组合以及任何形式的 Requires 语句的显式使用将引发编译器错误。

  使用 Requires 和使用 If-Then-Throw 语句之间有何差别? If-Then-Throw 语句在验证失败时始终引发您指示的异常。 在这一点上,它与 Requires 不同,但与 Requires<T> 相似。 纯 If-Then-Throw 语句也不会被约定工具(重写程序和静态检查程序)发现,除非您在该语句后调用 EndContractBlock。 使用 EndContractBlock 时,它必须是您在方法中调用的最后一个代码约定方法。 其后不能执行任何其他代码约定调用:

if (y == 0)
throw new ArgumentException();
Contract.EndContractBlock();  

  此外,Requires 语句是自动继承的。 除非您也使用 EndContractBlock,否则不会继承 If-Then-Throw 语句。 在旧模式中,不会继承 If-Then-Throw 约定。 实际上,您必须手动执行约定继承。 如果这些工具未检测到前置条件在重写和接口实现中重复,将尝试发出警告。

  最后,请注意,不允许 ContractAbbreviator 包含任何 If-Then-Throw 语句,但您可以对该属性使用约定验证程序。 缩写方法只能包含常规 Contract 语句进行参数验证。

  其他设置

  在代码约定属性页中,如果选中“执行运行时约定检查”选项,则将启用其他一些有用选项。

  如果启用“约定失败时断言”选项,则当违反约定时,将导致描述失败上下文的断言。 您将看到类似于图 2 中所示内容的消息框,并且可以选择一些选项。 例如,您可以再次尝试附加调试器,中止应用程序或者直接忽略失败并继续。

  您可能希望仅将此选项用于调试版本,因为显示的信息对于一般最终用户来说可能没有意义。 代码约定 API 提供了一个集中式异常处理程序用来捕获任何冲突,并由您判断错误的真正根源。 您收到的信息将区分是前置条件、后置条件还是固定条件失败,但仅使用布尔表达式并可能使用配置的错误消息来描述错误特征。 换句话说,从集中式异常处理程序轻松恢复有点难度:

Contract.ContractFailed += CentralizedErrorHandler;

  下面是说明处理程序的一些代码:

static void CentralizedErrorHandler(
Object sender, ContractFailedEventArgs e) {
Console.WriteLine("{0}: {1}; {2}", e.
FailureKind, e.Condition, e.Message);
e.SetHandled();
}       

  如果要在运行时引发特定异常,则可以使用 Requires<TException>。 如果您打算限制调试版本约定的使用或者如果您不关心异常的实际类型是什么,则可以使用 Requires 和集中式处理程序。 通常这足够指明发生了错误。 例如,许多应用程序在顶层都具有可捕获各种类型异常并指出如何重新启动的全能功能。

  “仅公共接口约定”选项是指您希望实施代码约定的位置:每个方法或仅公共方法。 如果选中该选项,重写程序将忽略代码约定语句的私有和受保护成员,并仅处理公共成员的约定。

  如果您将代码约定融入您的整体设计从而在任何地方使用代码约定,选中此选项很有意义。 但是,一旦应用程序做好交付准备,作为一种优化形式,您可以不必检查内部成员参数,因为没有外部代码直接调用这些成员。

  将代码约定限制为程序集的公共接口的想法是否好用,还取决于您编写代码的方式。 如果您可以保证公共接口对较低级别发出的任何调用都是正确的,则这是一种优化形式。 如果不能保证,禁用内部方法的约定可导致出现令人厌烦的错误。

  “调用网站需要检查”选项提供了另一种优化方案。 假设您正在编写要由其他程序集中的模块使用的库。 出于性能考虑,您禁止对约定进行运行时检查。 但是,只要您创建了约定引用程序集,就可以使调用方检查所调用的每个方法的约定。

  包含使用代码约定的类的每个程序集中都可能存在约定引用程序集。 其中包含具有 Contract 语句但没有其他代码的父程序集的公共可见接口。 可以从代码约定属性页对程序集的创建进行排序和控制。

  旨在调用库的任何代码都可能引用约定引用程序集,并且可以通过仅启用“调用网站需要检查”选项自动导入约定。 处理调用方代码时,重写程序将为随约定引用程序集一起提供的外部程序集上调用的每个方法导入约定。 这种情况下,将在调用站点(位于调用方侧)检查约定,并保留可对不直接控制的代码启用和禁用的可选行为。

  总结

  代码约定是 .NET Framework 的一个方面,值得进行更多研究。 我这里只对配置选项进行了简要的讨论,甚至未涉及静态检查程序的使用。 代码约定可帮助您更清楚地设计应用程序以及编写更简洁的代码。

  若要了解有关代码约定的详细信息,请参见 Melitta Andersen 2009 年 6 月的“CLR 全面透彻解析”专栏 (msdn.microsoft.com/magazine/ee236408) 和 DevLabs Code Contracts 网站 (msdn.microsoft.com/devlabs/dd491992)。 您还可以在 Microsoft Research 的代码约定网站 (research.microsoft.com/projects/contracts) 上找到有关代码约定开发的有趣背景信息。

一起谈.NET技术,Visual Studio 2010 中的代码约定设置相关推荐

  1. 图解Visual Studio 2010中的UML建模功能

    Visual Studio 2010在架构和项目管理方面的功能增加让人眼前一亮,之前曾详细介绍过Visual Studio 2010新增的架构工具,包括通过可视化技术DGML提供直观的图表,依赖图,继 ...

  2. 在Visual Studio 2010中创建多项目(解决方案)模板【三】

    前文回顾: 在Visual Studio 2010中创建多项目(解决方案)模板[一]:多项目解决方案模板的创建 在Visual Studio 2010中创建多项目(解决方案)模板[二]:Templat ...

  3. 如何在Visual Studio 2010中使用CppUTest建立TDD的Code Kata的环境

    CppUTest 是一个功能全面的测试框架,是为了支持在多种操作系统上开发嵌入式软件而特别设计的.CppUTest的宏被设计成不需要了解C++也可以写测试用例.这使得C程序员更容易用这个测试框架. 那 ...

  4. VB:使用Visual Studio 2010中的VB语言工具箱DataGridView调用SQL数据库Database的表格文件

    VB:使用Visual Studio 2010中的VB语言工具箱DataGridView调用SQL数据库Database的表格文件 目录 问题探究 图文教程 问题探究 使用Visual Studio ...

  5. Visual Studio 2010中C++项目升级指南

    如何升级? Visual Studio 2010支持来自VC6.Visual Studio 2002.Visual Studio 2003.Visual Studio 2005和Visual Stud ...

  6. 在Visual Studio 2010 中使用菱形向导对窗口进行布局

    "一个移动机器人,需要花大量的时间来进行运算,才可以避开环境中的简单的障碍物.人类也一样,只不过他们从未意识到罢了-直到光明消失在地平线.伴随着痛苦,他们才真正体会到需要进行的运算有多少.& ...

  7. Visual Studio 2010 中JS注释制作

    Visual Studio 2010中的js注释已经很强大了,但怎么才能和调用c#的方法一样容易呢?怎样才能让每个参数都有注释说明呢?底下就是想要的答案. 先上图,如图所示: 其中红色的办法为注释效果 ...

  8. VB.net:使用Visual Studio 2010中的VB.net语言工具箱DataGridView调用SQL数据库Database的表格文件

    VB.net:使用Visual Studio 2010中的VB.net语言工具箱DataGridView调用SQL数据库Database的表格文件 目录 问题探究 图文教程 问题探究 使用Visual ...

  9. 详解Visual Studio 2010中QT环境搭建配置

    本文将介绍如何在Visual Studio 2010中配置Qt这个跨平台应用程序和UI开发框架. QT简介 Qt是一个跨平台应用程序和UI开发框架.它包括跨平台类库.集成开发工具和跨平台 IDE.使用 ...

最新文章

  1. struts2学习笔记(二):Struts2配置
  2. 机器学习中的数学(3)-模型组合(Model Combining)之Boosting与Gradient Boosting
  3. linux关机_Linux系统管理:开机启动流程(一)
  4. ubutnu16.04安装ros2
  5. 自定义背景android,Android自定义Button并设置不同背景图片的方法
  6. synchronized 修饰static方法
  7. 单片机实验中用到的元器件(Multisim14)
  8. pdf怎么转图片,可得到高清图
  9. cesium绘制网格_Cesium (五) 栅格图层
  10. ascii码所有字符对照表(包含汉字和外国文字)
  11. 【推荐】“汇新杯”新兴科技+互联网创新大赛——IT人大展拳脚的创业大赛
  12. 为你解说加密Pdf如何导出图片
  13. 解析:Are We Really Making Much Progress?A Worrying Analysis of Recent Neural Recommendation Approaches
  14. maya多边形建模怎样做曲面_maya多边形建模教程_maya建模教程
  15. mysql数据库快捷键_MySQL数据库(YOG软件)快捷键大全
  16. html设置右缩进,word左右缩进怎么设置
  17. [leetcode]Majority Element II
  18. Vue3报错:Extraneous non-props attributes (ref_key) were passed to component but could not be automatic
  19. kali pinyin拼音输入法
  20. 是面试官放水,还是公司实在是太缺人?这都没挂,腾讯原来这么容易进···

热门文章

  1. lua学习笔记之开始
  2. spring中的单例工厂SingletonBeanRegistry设计与实现
  3. LeetCode Construct Quad Tree(dfs)
  4. 大型网站系统架构演化之路
  5. window下ffmpeg的编译
  6. LeetCode Minimum Depth of Binary Tree
  7. CentOS7和其他版本的虚拟机,防火墙命令等各种相关笔记
  8. Django笔记(一)
  9. Python3 模块 -- Fabric自动化模版
  10. ajax实现文件上传,多文件上传,追加参数