Visual Studio 2010 中的代码约定设置
合集下载
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
}
条件属性向编译器指示除非定义了CONTRACTS_FULL符号,否则应忽略此类方法调用。仅当您启用“执行运行时约定检查”选项时,才定义此符号。由于Contract.Requires<TException>不是根据条件定义的且缺少该属性,因此将执行重写程序检查,如果禁用运行时约定检查,则会导致失败的断言。
运行时检查
若要启用代码约定,必须选中“执行运行时约定检查”选项。如果未选中此选项,则在源代码中显示的任何约定说明将可能不会产生任何效果(定义了DEBUG符号的任何版本中的Contract.Assert和Contract.Assume例外,但这不是很重要)。复选框控制是否在每个编译步骤结束时触发重写程序工具。重写程序是一个外部工具,用于对软件约定进行后处理并修改MSIL代码,以及在合适的位置执行前置条件、后置条件和固定条件检查。
[Conditional("CONTRACTS_FULL")]
publicstaticvoidRequires(boolcondition,stringuserMessage) {
AssertMustUseRewriter(
ContractFailureKind.Precondition,"Requires");
图3不执行运行时约定检查时的反汇编视图
如果启用运行时检查,重写程序工具将通过编译器传递,返回并编辑MSIL代码。如果您在启用代码约定的情况下逐步执行相同代码,将看到类似图4的内容。
图4 Return语句后执行的后置 Nhomakorabea件检查明显的区别是在退出Sum方法之前且在return语句之后调用ValidateResult。您不必是MSIL专家就能了解图4中所示代码的状况。在方法开始接受最上面位置的前置条件之前,将对操作数进行验证。包含后置条件的代码将移动到方法的底部,最后一个return语句的MSIL代码将也是如此。更有意思的是,第一个return语句(Sum方法中实现快捷方式的语句)现在只跳到ValidateResult开始的地址:
“发行版要求”不支持Contract.Requires方法,仅允许使用Requires<TException>或旧的If-Then-Throw格式指定前置条件。
通过项目属性页可按项目启用或禁用运行时检查,但是如果您只想对代码的一些部分禁用运行时检查该怎么办?在这种情况下,只需使用ContractRuntimeIgnored属性以编程方式禁用运行时检查。但是,最新发行版(1.4.40307.0)中增加了新的“跳过限定符”选项,该选项也不允许您执行任何包含对Contract.ForAll或Contract.Exists的引用的约定。
Contract.Ensures(Contract.Result<Int32>() >=0);
}
如果您使用Contract.Requires而不是Contract.Requires<TException>,则在某个版本中关闭重写程序时不会出现图2所示的失败。图2中的消息框是由Contract.Requires的内部实现所致,如下所示:
接下来我们将考虑对上述代码使用重写程序的效果。您可以方便地亲自验证我所说的,方法是仅使用断点并按Ctrl+F11在Visual Studio 2010中打开反汇编视图。图3显示了在未启用运行时约定检查的情况下,逐步查看编译的Sum方法时反汇编视图的内容。正如您所看到的,源代码与您在类中编写的代码相同。
使用Requires和使用If-Then-Throw语句之间有何差别?If-Then-Throw语句在验证失败时始终引发您指示的异常。在这一点上,它与Requires不同,但与Requires<T>相似。纯If-Then-Throw语句也不会被约定工具(重写程序和静态检查程序)发现,除非您在该语句后调用EndContractBlock。使用EndContractBlock时,它必须是您在方法中调用的最后一个代码约定方法。其后不能执行任何其他代码约定调用:
[ContractAbbreviator]
privatevoidValidateOperands(Int32 x, Int32 y) {
Contract.Requires(x >=0&& y >=0);
}
[ContractAbbreviator]
privatevoidValidateResult() {
if(y ==0)
thrownewArgumentException();
Contract.EndContractBlock();
此外,Requires语句是自动继承的。除非您也使用EndContractBlock,否则不会继承If-Then-Throw语句。在旧模式中,不会继承If-Then-Throw约定。实际上,您必须手动执行约定继承。如果这些工具未检测到前置条件在重写和接口实现中重复,将尝试发出警告。
但是,请注意,如果您具有类似下面这样的前置条件,则在关闭重写程序时会得到运行时断言失败:
Contract.Requires<TException>(condition)
图2显示了您得到的消息框。
图2代码需要运行时约定检查
若要详细查看运行时检查的工作方式,请考虑以下代码:
publicInt32 Sum(Int32 x, Int32 y) {
代码约定是.NET Framework 4的一部分,但同样依赖于Visual Studio 2010中的一些功能,例如运行时工具、与MSBuild集成以及“项目属性”框中的属性页。值得注意的是,仅编写前置条件和后置条件是不够的。您还需要为每个项目启用运行时检查功能才能使用软件约定。您可以通过Visual Studio 2010中的“代码约定”项目属性页来完成上述操作。
Visual Studio 2010中的代码约定设置
软件约定称为代码约定,通过这一约定可以表示代码正常工作所需的正式条件。如果方法未按预期收到数据或生成的数据不符合预期的后置条件,代码约定将导致代码引发异常。有关前置条件和后置条件的概述,您可能需要查看我上个月发表的文章(/magazine/gg983479)。
最后,请注意,不允许ContractAbbreviator包含任何If-Then-Throw语句,但您可以对该属性使用约定验证程序。缩写方法只能包含常规Contract语句进行参数验证。
其他设置
在代码约定属性页中,如果选中“执行运行时约定检查”选项,则将启用其他一些有用选项。
如果启用“约定失败时断言”选项,则当违反约定时,将导致描述失败上下文的断言。您将看到类似于图2中所示内容的消息框,并且可以选择一些选项。例如,您可以再次尝试附加调试器,中止应用程序或者直接忽略失败并继续。
...
return 2 * x;
00000054 mov eax,dword ptr[ebp-8]
00000057 add eax,eax
00000059 mov dword ptr[ebp-0Ch],eax
0000005cnop
0000005d jmp 0000006B
...
ValidateResult();
如果您使用Requires和Requires<T>方法验证参数,程序集模式必须设置为“标准约定要求”。如果您使用任何If-Then-Throw语句作为前置条件,则应使用“自定义参数验证”。如果您不使用“自定义参数验证”,该语句将被视为Requires<T>。自定义参数验证的组合以及任何形式的Requires语句的显式使用将引发编译器错误。
}
publicstaticvoidRequires<TException>(boolcondition)
whereTException: Exception {
AssertMustUseRewriter(
ContractFailureKind.Precondition,"Requires<TException>");
可以对在Contract表达式中使用的成员应用属性。如果成员已使用此属性加以修饰,则显示该成员的整个Contract语句将不会进行运行时检查。属性不会在Assert和Assume等Contract方法中识别。
程序集模式
代码约定属性还可用于为约定配置“程序集模式”设置。此设置是指您打算执行参数验证的方式。有两个可能的选项:“标准约定要求”和“约定引用程序集”。程序集模式设置可帮助重写程序等工具在必要时优化代码并给出合适的警告。假设您使用程序集模式来声明您使用代码约定进行参数验证的意图。程序集模式设置引入了一些必须符合的简单规则,否则您将收到编译错误。
如果它是设计功能,则没理由剥离发行版中的约定。如果它仅是一种调试技术,当在发布模式中进行编译时,您不希望显示它。
在.NET Framework中,代码约定仅是此框架的一部分并且未融入任何语言。这样将更容易在项目中按版本配置它们。因此,通过软件约定的.NET Framework实现,您可以决定实现约定的合适时间和地点。
您可能希望仅将此选项用于调试版本,因为显示的信息对于一般最终用户来说可能没有意义。代码约定API提供了一个集中式异常处理程序用来捕获任何冲突,并由您判断错误的真正根源。您收到的信息将区分是前置条件、后置条件还是固定条件失败,但仅使用布尔表达式并可能使用配置的错误消息来描述错误特征。换句话说,从集中式异常处理程序轻松恢复有点难度:
// Check input values
ValidateOperands(x, y);
ValidateResult();
// Perform the operation
if(x == y)
return2* x;
returnx + y;
}
约定详细信息使用ContractAbbreviator存储在ValidateXxx方法中,如上个月的专栏所讨论。以下是ValidateXxx方法的源代码:
图1显示Visual Studio 2010中的属性页,通过此页可以设置软件约定为应用程序工作的方式。请注意,此类设置基于项目应用,因此可以根据需要进行调整。
图1 Visual Studio 2010中代码约定的属性页
您可以选择选项配置(调试、发布等)并仅对该配置应用设置。这样,您可以启用代码约定用于调试但不用于发布,而且更重要的是,您可以随时改变决策。
Contract.ContractFailed += CentralizedErrorHandler;
下面是说明处理程序的一些代码:
staticvoidCentralizedErrorHandler(
Object sender, ContractFailedEventArgs e) {
Console.WriteLine("{0}: {1}; {2}", e.
“仅公共接口约定”选项是指您希望实施代码约定的位置:每个方法或仅公共方法。如果选中该选项,重写程序将忽略代码约定语句的私有和受保护成员,并仅处理公共成员的约定。
在本文中,我将讨论您可以查看或选择的各个选项的预定用途,并深入讨论使用代码约定中的参数验证可以执行的最常见操作的重写程序工具和实践。
代码约定属性页
应在所有版本中还是仅在调试版本中实施代码约定前置条件和后置条件?实际上,这取决于您对软件约定概念的理解。它是设计工作的一部分吗?或者,它仅是一种调试措施?
0000006b push dword ptr ds:[02C32098h]
...
回到图1,请注意“执行运行时约定检查”复选框旁边的下拉列表。您可以通过该列表指示要启用的软件约定数目。存在多个级别:“完全”、“前置和后置”、“前置条件”、“发行版要求”和“无”。
“完全”表示支持所有类型的软件约定,“无”表示不考虑任何软件约定。“前置和后置”排除固定条件。“前置条件”还排除Ensure语句。
FailureKind, e.Condition, e.Message);
e.SetHandled();
}
如果要在运行时引发特定异常,则可以使用Requires<TException>。如果您打算限制调试版本约定的使用或者如果您不关心异常的实际类型是什么,则可以使用Requires和集中式处理程序。通常这足够指明发生了错误。例如,许多应用程序在顶层都具有可捕获各种类型异常并指出如何重新启动的全能功能。
条件属性向编译器指示除非定义了CONTRACTS_FULL符号,否则应忽略此类方法调用。仅当您启用“执行运行时约定检查”选项时,才定义此符号。由于Contract.Requires<TException>不是根据条件定义的且缺少该属性,因此将执行重写程序检查,如果禁用运行时约定检查,则会导致失败的断言。
运行时检查
若要启用代码约定,必须选中“执行运行时约定检查”选项。如果未选中此选项,则在源代码中显示的任何约定说明将可能不会产生任何效果(定义了DEBUG符号的任何版本中的Contract.Assert和Contract.Assume例外,但这不是很重要)。复选框控制是否在每个编译步骤结束时触发重写程序工具。重写程序是一个外部工具,用于对软件约定进行后处理并修改MSIL代码,以及在合适的位置执行前置条件、后置条件和固定条件检查。
[Conditional("CONTRACTS_FULL")]
publicstaticvoidRequires(boolcondition,stringuserMessage) {
AssertMustUseRewriter(
ContractFailureKind.Precondition,"Requires");
图3不执行运行时约定检查时的反汇编视图
如果启用运行时检查,重写程序工具将通过编译器传递,返回并编辑MSIL代码。如果您在启用代码约定的情况下逐步执行相同代码,将看到类似图4的内容。
图4 Return语句后执行的后置 Nhomakorabea件检查明显的区别是在退出Sum方法之前且在return语句之后调用ValidateResult。您不必是MSIL专家就能了解图4中所示代码的状况。在方法开始接受最上面位置的前置条件之前,将对操作数进行验证。包含后置条件的代码将移动到方法的底部,最后一个return语句的MSIL代码将也是如此。更有意思的是,第一个return语句(Sum方法中实现快捷方式的语句)现在只跳到ValidateResult开始的地址:
“发行版要求”不支持Contract.Requires方法,仅允许使用Requires<TException>或旧的If-Then-Throw格式指定前置条件。
通过项目属性页可按项目启用或禁用运行时检查,但是如果您只想对代码的一些部分禁用运行时检查该怎么办?在这种情况下,只需使用ContractRuntimeIgnored属性以编程方式禁用运行时检查。但是,最新发行版(1.4.40307.0)中增加了新的“跳过限定符”选项,该选项也不允许您执行任何包含对Contract.ForAll或Contract.Exists的引用的约定。
Contract.Ensures(Contract.Result<Int32>() >=0);
}
如果您使用Contract.Requires而不是Contract.Requires<TException>,则在某个版本中关闭重写程序时不会出现图2所示的失败。图2中的消息框是由Contract.Requires的内部实现所致,如下所示:
接下来我们将考虑对上述代码使用重写程序的效果。您可以方便地亲自验证我所说的,方法是仅使用断点并按Ctrl+F11在Visual Studio 2010中打开反汇编视图。图3显示了在未启用运行时约定检查的情况下,逐步查看编译的Sum方法时反汇编视图的内容。正如您所看到的,源代码与您在类中编写的代码相同。
使用Requires和使用If-Then-Throw语句之间有何差别?If-Then-Throw语句在验证失败时始终引发您指示的异常。在这一点上,它与Requires不同,但与Requires<T>相似。纯If-Then-Throw语句也不会被约定工具(重写程序和静态检查程序)发现,除非您在该语句后调用EndContractBlock。使用EndContractBlock时,它必须是您在方法中调用的最后一个代码约定方法。其后不能执行任何其他代码约定调用:
[ContractAbbreviator]
privatevoidValidateOperands(Int32 x, Int32 y) {
Contract.Requires(x >=0&& y >=0);
}
[ContractAbbreviator]
privatevoidValidateResult() {
if(y ==0)
thrownewArgumentException();
Contract.EndContractBlock();
此外,Requires语句是自动继承的。除非您也使用EndContractBlock,否则不会继承If-Then-Throw语句。在旧模式中,不会继承If-Then-Throw约定。实际上,您必须手动执行约定继承。如果这些工具未检测到前置条件在重写和接口实现中重复,将尝试发出警告。
但是,请注意,如果您具有类似下面这样的前置条件,则在关闭重写程序时会得到运行时断言失败:
Contract.Requires<TException>(condition)
图2显示了您得到的消息框。
图2代码需要运行时约定检查
若要详细查看运行时检查的工作方式,请考虑以下代码:
publicInt32 Sum(Int32 x, Int32 y) {
代码约定是.NET Framework 4的一部分,但同样依赖于Visual Studio 2010中的一些功能,例如运行时工具、与MSBuild集成以及“项目属性”框中的属性页。值得注意的是,仅编写前置条件和后置条件是不够的。您还需要为每个项目启用运行时检查功能才能使用软件约定。您可以通过Visual Studio 2010中的“代码约定”项目属性页来完成上述操作。
Visual Studio 2010中的代码约定设置
软件约定称为代码约定,通过这一约定可以表示代码正常工作所需的正式条件。如果方法未按预期收到数据或生成的数据不符合预期的后置条件,代码约定将导致代码引发异常。有关前置条件和后置条件的概述,您可能需要查看我上个月发表的文章(/magazine/gg983479)。
最后,请注意,不允许ContractAbbreviator包含任何If-Then-Throw语句,但您可以对该属性使用约定验证程序。缩写方法只能包含常规Contract语句进行参数验证。
其他设置
在代码约定属性页中,如果选中“执行运行时约定检查”选项,则将启用其他一些有用选项。
如果启用“约定失败时断言”选项,则当违反约定时,将导致描述失败上下文的断言。您将看到类似于图2中所示内容的消息框,并且可以选择一些选项。例如,您可以再次尝试附加调试器,中止应用程序或者直接忽略失败并继续。
...
return 2 * x;
00000054 mov eax,dword ptr[ebp-8]
00000057 add eax,eax
00000059 mov dword ptr[ebp-0Ch],eax
0000005cnop
0000005d jmp 0000006B
...
ValidateResult();
如果您使用Requires和Requires<T>方法验证参数,程序集模式必须设置为“标准约定要求”。如果您使用任何If-Then-Throw语句作为前置条件,则应使用“自定义参数验证”。如果您不使用“自定义参数验证”,该语句将被视为Requires<T>。自定义参数验证的组合以及任何形式的Requires语句的显式使用将引发编译器错误。
}
publicstaticvoidRequires<TException>(boolcondition)
whereTException: Exception {
AssertMustUseRewriter(
ContractFailureKind.Precondition,"Requires<TException>");
可以对在Contract表达式中使用的成员应用属性。如果成员已使用此属性加以修饰,则显示该成员的整个Contract语句将不会进行运行时检查。属性不会在Assert和Assume等Contract方法中识别。
程序集模式
代码约定属性还可用于为约定配置“程序集模式”设置。此设置是指您打算执行参数验证的方式。有两个可能的选项:“标准约定要求”和“约定引用程序集”。程序集模式设置可帮助重写程序等工具在必要时优化代码并给出合适的警告。假设您使用程序集模式来声明您使用代码约定进行参数验证的意图。程序集模式设置引入了一些必须符合的简单规则,否则您将收到编译错误。
如果它是设计功能,则没理由剥离发行版中的约定。如果它仅是一种调试技术,当在发布模式中进行编译时,您不希望显示它。
在.NET Framework中,代码约定仅是此框架的一部分并且未融入任何语言。这样将更容易在项目中按版本配置它们。因此,通过软件约定的.NET Framework实现,您可以决定实现约定的合适时间和地点。
您可能希望仅将此选项用于调试版本,因为显示的信息对于一般最终用户来说可能没有意义。代码约定API提供了一个集中式异常处理程序用来捕获任何冲突,并由您判断错误的真正根源。您收到的信息将区分是前置条件、后置条件还是固定条件失败,但仅使用布尔表达式并可能使用配置的错误消息来描述错误特征。换句话说,从集中式异常处理程序轻松恢复有点难度:
// Check input values
ValidateOperands(x, y);
ValidateResult();
// Perform the operation
if(x == y)
return2* x;
returnx + y;
}
约定详细信息使用ContractAbbreviator存储在ValidateXxx方法中,如上个月的专栏所讨论。以下是ValidateXxx方法的源代码:
图1显示Visual Studio 2010中的属性页,通过此页可以设置软件约定为应用程序工作的方式。请注意,此类设置基于项目应用,因此可以根据需要进行调整。
图1 Visual Studio 2010中代码约定的属性页
您可以选择选项配置(调试、发布等)并仅对该配置应用设置。这样,您可以启用代码约定用于调试但不用于发布,而且更重要的是,您可以随时改变决策。
Contract.ContractFailed += CentralizedErrorHandler;
下面是说明处理程序的一些代码:
staticvoidCentralizedErrorHandler(
Object sender, ContractFailedEventArgs e) {
Console.WriteLine("{0}: {1}; {2}", e.
“仅公共接口约定”选项是指您希望实施代码约定的位置:每个方法或仅公共方法。如果选中该选项,重写程序将忽略代码约定语句的私有和受保护成员,并仅处理公共成员的约定。
在本文中,我将讨论您可以查看或选择的各个选项的预定用途,并深入讨论使用代码约定中的参数验证可以执行的最常见操作的重写程序工具和实践。
代码约定属性页
应在所有版本中还是仅在调试版本中实施代码约定前置条件和后置条件?实际上,这取决于您对软件约定概念的理解。它是设计工作的一部分吗?或者,它仅是一种调试措施?
0000006b push dword ptr ds:[02C32098h]
...
回到图1,请注意“执行运行时约定检查”复选框旁边的下拉列表。您可以通过该列表指示要启用的软件约定数目。存在多个级别:“完全”、“前置和后置”、“前置条件”、“发行版要求”和“无”。
“完全”表示支持所有类型的软件约定,“无”表示不考虑任何软件约定。“前置和后置”排除固定条件。“前置条件”还排除Ensure语句。
FailureKind, e.Condition, e.Message);
e.SetHandled();
}
如果要在运行时引发特定异常,则可以使用Requires<TException>。如果您打算限制调试版本约定的使用或者如果您不关心异常的实际类型是什么,则可以使用Requires和集中式处理程序。通常这足够指明发生了错误。例如,许多应用程序在顶层都具有可捕获各种类型异常并指出如何重新启动的全能功能。