我刚刚安装了新发布的 Visual Studio 2017 Enterprise (RC)。但是,我无法让它与Code Contracts一起使用。我在 Visual Studio 2015 中使用代码合同没有问题。我错过了什么吗?
5 回答
正如其他人所指出的,微软没有优先考虑代码合同,其长期支持仍不清楚(尽管通过Roslyn正在进行一些关于语言级集成的讨论)。
然而,截至2017 年 3 月 11 日,社区贡献者Yaakov至少更新了源代码以包含Visual Studio 2017 构建目标(谢谢!)。此版本支持编译期间的静态检查,以及使用CCRewrite
.
注意:此版本不通过项目的属性窗格提供配置支持。因此,需要通过手动将适当的属性添加到
csproj
文件来配置代码合同。有关属性的完整列表,请参阅下面的@crimbo 答案。
不幸的是,虽然这些更新已合并到主代码分支中,但它们既没有反映在Marketplace 分发版中,也没有反映在官方NuGet 包中。因此,您需要从存储库下载并编译源代码(这很简单;只需使用提供的BuildCC.bat
文件)。
重要提示:代码协定的静态分析对.NET 3.5具有硬编码依赖性,默认情况下, Windows 10或Visual Studio 2017中不再安装。因此,您需要确保启用此“功能”(或单独下载);否则,你会得到一个编译时错误。
或者,截至2017 年 6 月 15 日(后来于 2018 年 2 月 6 日更新),贡献者Igor Bek已将此更新包含在他的NuGet 包中,因此最简单的方法是添加CodeContracts.MSBuild
到您的packages.config
via:
Install-Package CodeContracts.MSBuild -Version 1.12.0
背景:Igor Bek首先将这个包放在一起作为代码合同团队的概念验证,后来它成为官方 NuGet 包(在 v1.10.10126.2 中)的基础。由于微软还没有更新官方的 NuGet 包,所以他现在是最新的。
鉴于当前的支持状态,我不鼓励人们为新项目采用代码契约,但这应该为已经投资于现有 .NET Framework 项目的代码契约的开发人员提供向后兼容性。
在撰写本文时,没有 VS2017 的合约定义,但如果使用Nuget 包 DotNet.Contracts ,您可以通过以下方式解决它:
- 导航到 CodeContracts nuget 包目录 (
DotNet.Contracts.1.10.20606.1\MsBuild
) - 复制
v14.0
文件夹 - 将其重命名为
v15.0
一切都应该按预期构建。
代码合约在 VS 2017 中不起作用的原因是:
- 代码协定 MSBuild 文件未在 VS 2017 的 msbuild 文件树中导入(易于修复)
- VS 2017 项目属性中不存在代码合同配置 UI(通过包含 CodeContracts msbuild 属性轻松修复)
当然,关于 CodeContracts 未来的问题是有效的,但您可以实现以下内容以使使用 CodeContracts 的现有项目能够在 VS 2017 中构建:
将 的内容添加
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/cCodeContractsInstallDir
指定为环境变量。在这种情况下,您只需添加
<Import Condition="'$(CodeContractsImported)' != 'true' AND '$(DontImportCodeContracts)' != 'true'" Project="$(CodeContractsInstallDir)MsBuild\v$(VisualStudioVersion)\Microsoft.CodeContracts.targets" />
到您的 *.csproj 文件。
在 *.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#include-msbuild-props-and-targets-包内
如果有足够的兴趣,我愿意在 GitHub 上提供一个示例。
当前没有支持 Visual Studio 2017的 .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。
我发现这里建议的方法并不简单,特别是它需要在每个开发人员的机器和构建服务器上进行更改。
我决定创建我自己的非常简化的版本,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://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");