【问题标题】:Does Visual Studio 2017 work with Code Contracts?Visual Studio 2017 是否适用于代码合同?
【发布时间】:2020-06-20 13:04:48
【问题描述】:

我刚刚安装了新发布的 Visual Studio 2017 Enterprise (RC)。但是,我无法让它与Code Contracts 一起使用。我在 Visual Studio 2015 中使用代码合同没有任何问题。我错过了什么吗?

【问题讨论】:

标签: c# .net visual-studio-2017 code-contracts


【解决方案1】:

正如其他人所指出的,Microsoft 并没有优先考虑代码合同及其 long-term support remains unclear(尽管有一些 ongoing discussion about language-level integration 来自 Roslyn)。

不过,截至March 11th, 2017,社区贡献者Yaakov 至少更新了source code 以包含Visual Studio 2017 build targets(谢谢!)。此版本支持编译期间的静态检查,以及使用CCRewrite 的运行时验证。

注意:此版本不通过project's properties pane 提供配置支持。因此,需要通过手动将适当的属性添加到csproj 文件来配置代码协定。请参阅下面@crimbo 的答案以获取comprehensive list of properties。

很遗憾,虽然这些更新已经合并到主代码分支中,但它们既没有反映在Marketplace distribution 也没有反映在官方的NuGet Package 中。因此,您需要从存储库下载并编译source code(这很简单;只需使用提供的BuildCC.bat 文件)。

重要提示:代码契约的静态分析对 .NET 3.5 有硬编码依赖,在任一 Windows 10 中都不再默认安装 或 Visual Studio 2017。因此,您需要确保启用此“功能”(或download it separately);否则,你会得到一个编译时错误。

或者,从 June 15th, 2017 开始(后来于 2018 年 2 月 6 日更新),贡献者 Igor Bek 已将此更新包含在他的 NuGet Package 中,因此最简单的方法是将 CodeContracts.MSBuild 添加到您的 packages.config通过:

Install-Package CodeContracts.MSBuild -Version 1.12.0

背景:Igor Bek首先将这个包放在一起作为proof-of-concept for the Code Contracts team,后来成为official NuGet package的基础(在v1.10.10126.2中)。由于微软还没有更新官方的 NuGet 包,所以他现在是最新的。

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

【讨论】:

  • 仅供参考:我已验证此信息在此评论时保持最新。我将评论更改为链接到 Igor Bek 的 NuGet 包的后续更新,该包自我最初提交此答案以来已发布。
【解决方案2】:

在撰写本文时,没有 VS2017 的合约定义,但如果使用 Nuget package DotNet.Contracts,您可以通过以下方式解决它:

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

一切都应该按预期构建。

【讨论】:

  • 感谢您的回复。看起来我必须回到基本的......你如何在VS2015中使用CodeContracts nuget包?您是否需要安装额外的组件(例如 Contracts.devlab9ts.msi)?
  • 没有安装 Contracts.devlab9ts.msi,我在项目的属性中看不到“代码合同”选项卡。
  • user6706499 - 你是如何让它工作的?你必须安装 Contracts.devlab9ts.msi 吗?
  • Igor - 我注意到您已经编辑了答案。我想知道你是否已经让它与像 Contract.Requires(condition) 这样的代码一起工作,这需要 ccrewrites?
  • @user2106007 - 查看@JeremyCaney 的答案,我安装了NuGet 包CodeContracts.MSBuild。您只需编辑 .csproj 文件以指定配置即可。
【解决方案3】:

目前没有支持 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产品名称,例如社区。​​p>

这将允许您在 VS2017 中使用代码合同进行构建,但不会解决 CC 选项卡未显示在项目设置中的问题。为此,您仍然需要切换到 VS2015。

【讨论】:

  • 谢谢爱丁。如果项目已经配置为使用它,这是否意味着所有代码合同功能都可以使用?
  • 是的,没错,但我建议做一些测试以确保您的 dll 是用 CC 重写的。
  • 谢谢爱丁。鉴于 Microsoft CC 的不确定性,我们决定继续前进,我们花了大约一周的时间将 Microsoft CC 替换为内部开发的小型 CC 框架,以保留 CC 概念及其大部分功能。但是我们真的很怀念接口契约——我相信你明白我的意思。
  • 另一种选择是利用 Post#(这是商业的,但与滚动你自己的相比不太贵)。语法不同(利用属性)但具有相同的功能。
  • 帮助别人找到它:PostSharp
【解决方案4】:

代码合约在 VS 2017 中不起作用的原因是:

  1. 代码协定 MSBuild 文件未导入到 VS 2017 的 msbuild 文件树中(易于修复)
  2. 代码合同配置 UI 不存在于 VS 2017 项目属性中(通过包含 CodeContracts msbuild 属性轻松修复)

当然,关于 CodeContracts 未来的问题是有效的,但您可以实施以下方法以使使用 CodeContracts 的现有项目能够在 VS 2017 中构建:

  1. 将C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.Targets\ImportAfter\CodeContractsAfter.targets 的内容添加到您的csproj 文件中(直接或间接通过导入)。最基本的方法是将其添加到您的 csproj 文件中:

    <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,b/c CodeContractsInstallDir 应指定为环境变量。在这种情况下,您只需添加即可逃脱

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

到您的 *.csproj 文件。

  1. 在您的 *.csproj 文件中指定所有 CodeContracts 属性(直接或通过 Import 间接)。例如:

    <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 包。代码协定设置(来自第 2 步)可以进入您的 mycompany.codecontracts.props 文件,代码协定目标(来自第 1 步)可以进入您的 mycompany.codecontracts.targets 文件。

有关在 nuget 包中打包 msbuild 属性/目标的更多信息:https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package#including-msbuild-props-and-targets-in-a-package

如果有足够的兴趣,我愿意在 GitHub 上提供一个示例。

【讨论】:

  • @crimbo:感谢您的详细回答。在编译过程中,将它放在适当的位置似乎没有伤害任何东西,但它似乎也没有正确执行 CCRewrite。因此,在运行时,我收到错误消息:“必须使用代码协定二进制重写器 (CCRewrite) 重写汇编(可能是“…”),因为它正在调用 Contract.Requires 并且定义了 CONTRACTS_FULL 符号。”这是预期的吗? Visual Studio 2017 中是否有支持 CCRewrite 的场景?
  • @crimbo 仅供参考:我通过使用较新的CodeContracts.MSBuild NuGet 包解决了上述问题,其中包括对 Visual Studio 2017 支持所需的代码协定的更新(至少在 MSBuild 流程方面) )。有了这些,我看到静态代码分析和重写都在发生。这也减少了在代码合约中修改构建目标或手动将它们导入csproj 文件的需要。
  • @JeremyCaney :我也尝试过 CodeContracts.MSBuild NuGet 包。这基本上处理了上面的步骤 1,但不处理步骤 2(它不适合我)。除非定义了 CodeContracts 属性,否则 ccrewrite 不会运行。它们可以在 VS 2015 中通过项目设置 UI(将它们添加到您的 .csproj)中进行设置;或者它们可以手动添加或从文件中导入。
  • @crimbo:这是一个很好的说明;谢谢你。就我而言,我的csproj 文件中已经有了这些设置。但它们绝对是启用静态检查和运行时验证的必要条件。
【解决方案5】:

我发现这里建议的方法并不简单,特别是它需要在每个开发人员的机器上以及构建服务器上进行更改。

我决定创建自己的非常简化版本的Contract.Requires(),它只需要全局替换调用者类中的using 声明。

using MYCommon.Diagnostics; //System.Diagnostics.Contracts;

当/如果 System.Diagnostics.Contracts will be available for VS 2017 和 .NetStandard,很容易恢复到正确的版本。

实际的类是:

    /// <summary>
    ///   Contract.Requires(config != null); in VS 2017 not throw  ArgumentNullException
    /// The class is workaround for https://stackoverflow.com/questions/40767941/does-vs2017-work-with-codecontracts 
    /// </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://stackoverflow.com/questions/41397/asking-a-generic-method-to-throw-specific-exception-type-on-fail/41450#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");

【讨论】:

  • 如果您主要关心Requires() 的运行时验证,这是一种有效的策略。它可以扩展到支持Assert()、Assume()、Exists() 和ForAll(),而无需付出太多努力。但是,如果需要它们,使用这种方法支持Ensures()、Invariant()、Result() 或ValueAtReturn() 将是不切实际的(无需对调用者进行重大重构)。换句话说,这实际上只能作为验证先决条件的简写。
  • 不幸的是,缺乏Ensure 的支持使这一点值得商榷。另一方面,白色Require 很容易,Ensure 非常困难。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-09
  • 2018-04-08
  • 2017-04-27
  • 2018-03-16
  • 1970-01-01
  • 2017-10-03
相关资源
最近更新 更多