Visual Studio 2017能够使用Code Contracts吗?

52

我刚刚安装了新发布的Visual Studio 2017 Enterprise(RC)。然而,我在将其与Code Contracts一起使用时遇到了问题。但是,在Visual Studio 2015中使用Code Contracts没有任何问题。我有什么遗漏吗?


能否有微软的人发表一下评论呢? - user2106007
有微软VS团队的人吗??? VS2017是否与CodeContracts兼容? - user2106007
3
在Github上已经开了一个官方问题"CodeContracts not working in VS2017" #476,同时也有Visual Studio 2017 support #451的支持。 - Michael Freidgeim
5个回答

27
作为其他人指出的,微软并没有优先考虑Code Contracts,并且它的长期支持仍不清楚(尽管已经有一些通过Roslyn进行语言级别整合的持续讨论)。
然而,截至2017年3月11日,社区贡献者Yaakov已经更新了源代码,包括Visual Studio 2017构建目标(谢谢!)。这个版本提供了编译期间的静态检查支持,以及使用CCRewrite进行运行时验证的支持。

注意:此版本不支持通过项目属性窗格进行配置。因此,代码合同需要通过手动向csproj文件添加适当的属性来配置。有关属性列表的详细信息,请参见@crimbo的答案。

不幸的是,尽管这些更新已合并到主代码分支中,但它们既没有反映在Marketplace distribution中,也没有反映在官方NuGet Package中。因此,您需要从存储库下载和编译源代码(这很容易;只需使用提供的BuildCC.bat文件)。

重要提示:Code Contracts的静态分析对.NET 3.5有硬编码依赖,而这在Windows 10或Visual Studio 2017中默认不再安装。因此,您需要确保启用此“功能”(或单独下载),否则将收到编译时错误。
另外,自2017年6月15日起,贡献者Igor Bek已将此更新包含在他的NuGet Package中,并于2018年2月6日进行了更新。因此,最简单的方法是通过以下方式将CodeContracts.MSBuild添加到您的packages.config中:
Install-Package CodeContracts.MSBuild -Version 1.12.0
背景Igor Bek最初将此软件包组合在一起,作为代码契约团队的概念验证, 后来它成为了官方NuGet软件包的基础(在v1.10.10126.2中)。由于Microsoft没有更新官方NuGet软件包,因此他的软件包现在是最新的。

鉴于目前的支持状态,我不鼓励人们为新项目采用代码契约,但这应该为已经投资于现有.NET Framework项目的开发人员提供向后兼容性。


1
FYI:我已经验证了这些信息截至本评论仍然是最新的。我更改了评论以链接到Igor Bek的NuGet包的更新版本,因为自我最初提交答案以来已经发布。 - Jeremy Caney

14

目前为止,在VS2017中还没有合同定义,但是如果使用Nuget包DotNet.Contracts,则可以通过以下方法解决:

  • 导航到CodeContracts nuget包目录(DotNet.Contracts.1.10.20606.1\MsBuild
  • 复制v14.0文件夹
  • 将其重命名为v15.0

一切都应该如预期地构建。


没有安装Contracts.devlab9ts.msi,我就看不到项目属性中的“Code Contracts”选项卡。 - user2106007
我在我的开发机上创建了一个测试项目,安装了VS2015和VS2017。在VS2015中打开该项目,我可以看到“代码合同”选项卡。但是,在VS2017中打开该项目,我没有看到“代码合同”选项卡。有什么解决方法?信息? - user2106007
你是一个英雄!! - Brendan
1
Igor - 我注意到你已经编辑了答案。我想知道你是否让它能够与像 Contract.Requires<ArgumentException>(condition) 这样需要 ccrewrites 的代码一起工作? - user2106007
2
@user2106007 - 请查看 @JeremyCaney 的答案,我安装了 NuGet 包 CodeContracts.MSBuild。这个和编辑 .csproj 文件以指定配置是你需要做的全部。 - Igor
显示剩余2条评论

7
代码合同在VS 2017中无法正常工作的原因有以下两点:
1.代码合同MSBuild文件未在VS 2017的msbuild文件树中导入(易于修复)。 2.代码合同配置界面在VS 2017项目属性中不存在(通过包含CodeContracts msbuild属性轻松解决)。
当然,关于CodeContracts的未来问题很重要,但您可以执行以下操作以使使用CodeContracts的现有项目能够在VS 2017中构建:
  1. Add the contents of C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.Targets\ImportAfter\CodeContractsAfter.targets to your csproj files (either directly or indirectly via an import). The most basic approach is to add this to your csproj file:

    <PropertyGroup>
      <CodeContractsInstallDir Condition="'$(CodeContractsInstallDir)'==''">C:\Program Files (x86)\Microsoft\Contracts\</CodeContractsInstallDir>
    </PropertyGroup>
    <Import Condition="'$(CodeContractsImported)' != 'true' AND '$(DontImportCodeContracts)' != 'true'" Project="$(CodeContractsInstallDir)MsBuild\v$(VisualStudioVersion)\Microsoft.CodeContracts.targets" />
    
请注意,如果安装了CodeContracts,则第一个PropertyGroup不是必需的,因为CodeContractsInstallDir应该被指定为环境变量。在这种情况下,您只需要添加以下代码即可:

<Import Condition="'$(CodeContractsImported)' != 'true' AND '$(DontImportCodeContracts)' != 'true'" Project="$(CodeContractsInstallDir)MsBuild\v$(VisualStudioVersion)\Microsoft.CodeContracts.targets" />

添加到您的 *.csproj 文件中。

  1. Specify all the CodeContracts properties in your *.csproj file (directly or indirectly via Import). Eg:

    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    
    <!-- Code Contracts settings -->
    <PropertyGroup>
      <CodeContractsAssemblyMode>1</CodeContractsAssemblyMode>
      <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking>
      <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface>
      <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure>
      <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires>
      <CodeContractsRuntimeSkipQuantifiers>False</CodeContractsRuntimeSkipQuantifiers>
      <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis>
      <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations>
      <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations>
      <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations>
      <CodeContractsEnumObligations>False</CodeContractsEnumObligations>
      <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions>
      <CodeContractsInferRequires>False</CodeContractsInferRequires>
      <CodeContractsInferEnsures>False</CodeContractsInferEnsures>
      <CodeContractsInferObjectInvariants>False</CodeContractsInferObjectInvariants>
      <CodeContractsSuggestAssumptions>False</CodeContractsSuggestAssumptions>
      <CodeContractsSuggestRequires>True</CodeContractsSuggestRequires>
      <CodeContractsSuggestEnsures>False</CodeContractsSuggestEnsures>
      <CodeContractsSuggestObjectInvariants>False</CodeContractsSuggestObjectInvariants>
      <CodeContractsDisjunctiveRequires>False</CodeContractsDisjunctiveRequires>
      <CodeContractsRunInBackground>True</CodeContractsRunInBackground>
      <CodeContractsShowSquigglies>False</CodeContractsShowSquigglies>
      <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine>
      <CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs>
      <CodeContractsCacheAnalysisResults>True</CodeContractsCacheAnalysisResults>
      <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel>
      <CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly>
      <CodeContractsAnalysisWarningLevel>0</CodeContractsAnalysisWarningLevel>
    </PropertyGroup>
    
    <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
    </PropertyGroup>
    
    <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
      <CodeContractsRuntimeCheckingLevel>ReleaseRequires</CodeContractsRuntimeCheckingLevel>
    </PropertyGroup>
    
    </Project>
    
如果你有多个项目,我建议将它们打包到一个私有的nuget包中,并在每个项目中引用该nuget包。代码合同设置(来自第二步骤)可以放在mycompany.codecontracts.props文件中,而代码合同目标(来自第一步骤)可以放在mycompany.codecontracts.targets文件中。
关于如何将msbuild属性/目标打包到nuget包中的更多信息,请点击这里:https://learn.microsoft.com/en-us/nuget/create-packages/creating-a-package#including-msbuild-props-and-targets-in-a-package 如果有充分的兴趣,我愿意在GitHub上提供示例。

@crimbo:感谢您详细的回答。在编译过程中,将其放置在这里似乎不会对任何东西造成伤害,但它也似乎无法正确执行CCRewrite。因此,在运行时,我会收到以下错误信息:“必须使用代码合同二进制重写器(CCRewrite)重新编写汇编(可能是“…”),因为它调用Contract.Requires<TEception>并定义了CONTRACTS_FULL符号。”这是预期的吗?在Visual Studio 2017中是否有支持CCRewrite的场景? - Jeremy Caney
1
@crimbo FYI:我通过使用更新的CodeContracts.MSBuild NuGet包解决了上述问题,该包包含了Visual Studio 2017支持所需的代码合同更新(至少在MSBuild过程方面)。有了这个,我可以看到静态代码分析和重写同时进行。这也减轻了修改代码合同中的构建目标或手动将其导入到csproj文件的需要。 - Jeremy Caney
1
@JeremyCaney:我也尝试使用了 CodeContracts.MSBuild NuGet 包。这基本上处理了上面的步骤1,但没有处理步骤2(对我来说并没有)。ccrewrite 不会运行除非定义了 CodeContracts 属性。它们可以通过 VS 2015 的项目设置 UI 进行设置(这会将它们添加到你的 .csproj 中),或者可以手动添加或从文件中导入。 - crimbo
@crimbo:这是一个很好的澄清;谢谢你。在我的情况下,我已经在我的csproj文件中设置了它们。但是,它们绝对是必要的,否则无法同时启用静态检查和运行时验证。 - Jeremy Caney

7
目前还没有支持 Visual Studio 2017 的 Code Contracts for .NET 版本。但是,如果您复制以下目标文件,该问题可以得到解决。
C:\Program Files (x86)\MSBuild\4.0\Microsoft.Common.Targets\ImportAfter\CodeContractsAfter.targets

将文件导入到您的VS2017 MSBuild的ImportAfter位置:
C:\Program Files (x86)\Microsoft Visual Studio\2017\#YourVS2017Product#\MSBuild\15.0\Microsoft.Common.targets\ImportAfter

注意:在上面的路径中替换#YourVS2017Product#为您的VS2017产品名称,例如Community。
这将允许您在VS2017中使用代码合同进行构建,但不能解决项目设置中未显示CC选项卡的问题。 为此,您仍需要切换到VS2015。

谢谢Edin。这是否意味着,如果一个项目已经配置了使用它,所有的代码合同功能都将起作用? - user2106007
是的,没错,但我建议进行一些测试,以确保您的dll文件已经使用CC进行了重写。 - Edin
谢谢你,Edin。鉴于微软CC的不确定未来,我们决定前进,花了大约一周的时间用一个小型内部开发的CC框架替换了微软CC,以保留CC概念和大部分功能。然而,我们真的很想念接口契约 - 我相信你知道我在说什么。 - user2106007
1
另一个选择是利用Post#(商业软件,但与自己开发相比不算太贵)。语法不同(利用属性),但具有相同的功能。 - Jimmy Zimms
1
为了帮助其他人找到它:PostSharp - Artyom
@user2106007:我很好奇你们的组织是否愿意开源(或者至少发布)你们内部开发的CC框架?即使不考虑为其他开发人员节省一周的开发时间,我认为这是一项可以从集体贡献中受益的事情。我也很好奇你们是如何解决后置条件的问题的,因为在没有重构调用代码的情况下复制它们似乎是最困难的部分(例如,在成员末尾放置“Ensures()”)。 - Jeremy Caney

1
我发现这里提出的方法并不直接,特别是需要在每个开发者的机器和构建服务器上进行更改。
因此,我决定创建自己的Contract.Requires()非常简化版,只需要在调用类中全局替换using声明即可。
using MYCommon.Diagnostics; //System.Diagnostics.Contracts;

如果/当System.Diagnostics.Contracts在VS 2017和.NetStandard中可用,那么恢复到正确版本将变得更加容易。

实际的类是:

    /// <summary>
    ///   Contract.Requires(config != null); in VS 2017 not throw  ArgumentNullException
    /// The class is workaround for https://dev59.com/tFkR5IYBdhLWcg3w1wch 
    /// </summary>
    public class Contract
    {
        public static void Requires(bool condition, string message = null)
        {
            Requires<ArgumentNullException>(condition, message);
        }
        public static void Requires<TException>(bool condition, string message=null) where TException:Exception , new ()
        {
            if (!condition)
            {
                //https://dev59.com/kXVD5IYBdhLWcg3wQJKT#41450
                var e=default(TException);
                try
                {
                    message = message ?? "Unexpected Condition"; //TODO consider to pass condition as lambda expression
                    e =  Activator.CreateInstance(typeof(TException), message) as TException;
                }
                catch (MissingMethodException ex)
                {
                    e = new TException();
                }
                throw e;
            }
        }
    }

基本限制在于典型用法Contract.Requires(param1!=null);不能让我抛出参数名称的异常,更好的用法稍微长一些:

Contract.Requires<ArgumentNullException>(param1!=null, "param1 is null");

2
如果您的主要关注点是Requires()的运行时验证,那么这是一种有效的策略。它可以扩展到支持Assert()Assume()Exists()ForAll(),而不需要太多的努力。然而,如果需要支持Ensures()Invariant()Result()ValueAtReturn(),使用这种方法将是不切实际的(除非对调用者进行重大重构)。换句话说,这只适用于验证前置条件的简写。 - Jeremy Caney
1
不幸的是,缺乏对Ensure的支持使得这个问题存在争议。另一方面,使用Require很容易,但使用Ensure则非常困难。 - Wiktor Zychla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接