为什么我的C#代码出现了格式错误的合同?

4

当我编写以下合同时,Visual Studio会显示错误。

Error 20 Malformed contract section in method '....get_Page'

问题出在if块吗?

public int? Page
{
  get
  {
    int? result = Contract.Result<int?>();

    if (result != null)
        Contract.Ensures(result >= 0);

    return default(int?);
  }
}

编辑:

Lasse V. Karisen在评论中发表了以下内容:

如何使用:Contract.Ensures(result == null || result >= 0); ?

是的,Karisen,我之前尝试过这个方法,并且它可以编译。但问题仍然存在:在使用合同时是否可能有if语句?

我遇到的另一个问题是无法理解(主要是考虑到上面的示例可行),也涉及到result的使用:

public int IndexOf(T item)
{
    Contract.Assert(item != null);
    Contract.Assert((item as IEntity).ID > 0);

    int result = Contract.Result<int>();
    Contract.Ensures(result >= -1);

    return default(int);
}

3
怎么样:Contract.Ensures(result == null || result >= 0)? (该代码行可能是在进行代码合约的编写,以确保方法返回值符合特定要求。这句话的意思是,如果结果为null,则满足该条件,或者如果结果大于或等于0,则同样满足该条件。) - Lasse V. Karlsen
6
老实说,我还没有看过C# 4.0的代码契约,我仍然忙于C# 3.0和.NET 3.5的工作。但如果称其为“契约”的目的就是它本身,那么请记住,规定契约何时有效的标准写在契约中而不是契约外部。你没有一个契约说其他契约何时有效。所以对我来说,这听起来应该是这样的,你必须编写一个明确说明一切都很好的契约,并且该契约不能依赖外部标准。 - Lasse V. Karlsen
5个回答

10

合同格式不正确,因为所有合同条款必须出现在任何其他代码之前。


1
是的,在C#中所有的合约都放在开头。(在Eiffel中,Requires放在开头,Ensures放在结尾。) - ctrl-alt-delor

2
你不需要使用if语句来进行布尔运算,可以使用implies!
public int? Page
{
    get
    {
        Contract.Ensures( (result!= null).Implies(result >= 0) );
        var result = ...

        ...


        return result;
    }
}

你在测试方法参数和其他前提条件时,应该使用Requires而不是Assert。
public int IndexOf(T item)
{
    Contract.Requires(item != null);
    Contract.Requires((item as IEntity).ID > 0);
...

Imply到底来自哪里?Ensures是一个Void方法,因此不应该能够在之后使用任何点符号... - bkqc
@bkqc implies 不是在 Ensures 上调用的,它是 bool 的一个方法(或者是 bool 的扩展方法)。 - ctrl-alt-delor
对啊!我应该学会认真读:P 我想象了一个额外的括号哈哈。这是在 .Net Core 中吗?因为我在 .NET Framework 中找不到它。你能告诉我它所在的 dll/assembly 吗(也许是 F12)? - bkqc

1
所有的Ensures和Requires调用必须在方法或属性体中的所有其他语句之前,包括像您正在使用的简单赋值语句这样有助于可读性的语句。
适当的语法
public int? Page {
    get {
        Contract.Ensures(Contract.Result<int?>() == null 
            || Contract.Result<int?>() >= 0); 

        return default(int?);
        }
    }
 }

这很丑,比普通的if (x || y) throw new ArgumentOutOfRangeException()要丑得多。

特殊属性

有一个迂回的方法可以解决这个问题。 ContractAbbreviatorAttributeContractArgumentValidatorAttributeccrewrite理解的特殊属性,可以让您的生活更轻松。(有关详细信息,请参见MSDN上的System.Diagnostics.Contracts命名空间文档或Code Contracts manual。)

如果使用 .NET 4 或更早版本: 这些属性从 .NET 4.5 开始出现在框架中,但是对于早期版本,您可以从 Code Contracts 安装的目录(C:\Program Files (x86)\Microsoft\Contracts\Languages\)中获取它们的源文件。在该文件夹下有 CSharpVisualBasic 子文件夹,其中包含了一个 ContractExtensions.cs (或 .vb) 文件,其中包含所需代码。 ContractAbbreviatorAttribute 这个属性有效地让您创建合同宏。 使用它,您的页面属性可以像这样编写:
public int? Page {
    get {
        EnsuresNullOrPositive();
        return default(int?)
    }
}

[ContractAbbreviator]
static void EnsuresNullOrPositive(int? x) {
    Contract.Ensures(
        Contract.Result<int?>() == null ||                 
        Contract.Result<int?>() >= 0);
}

EnsuresNullOrPositive也可以放在静态类中并在整个项目中重复使用,或者将其公开并放在实用程序库中。您还可以像下一个示例一样使其更通用。

[ContractAbbreviator]
static void EnsuresNullOrPositive<Nullable<T>>(Nullable<T> obj) {
    Contract.Ensures(
        Contract.Result<Nullable<T>>() == null ||                 
        Contract.Result<Nullable<T>>() >= default(T));
}

对于我的实用库,我有一个名为Requires的静态类和一个名为Ensures的静态类,每个类都有许多带有ContractAbbreviator修饰的静态方法。以下是一些示例:

public static class Requires {

    [ContractAbbreviator] 
    public static void NotNull(object obj) {  
        Contract.Requires<ArgumentNullException>(obj != null);
    }

    [ContractAbbreviator]
    public static void NotNullOrEmpty(string str) {
        Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(str));
    }

    [ContractAbbreviator]
    public static void NotNullOrEmpty(IEnumerable<T> sequence) {
        Contract.Requires<ArgumentNullException>(sequence != null);
        Contract.Requires<ArgumentNullException>(sequence.Any());
    }
}

public static class Ensures {
    [ContractAbbreviator]
    public static void NotNull(){
        Contract.Ensures(Contract.Result<object>() != null);
    }
}

这些可以像这样使用:
public List<SentMessage> EmailAllFriends(Person p) {
    Requires.NotNull(p); //check if object is null
    Requires.NotNullOrEmpty(p.EmailAddress); //check if string property is null or empty
    Requires.NotNullOrEmpty(p.Friends); //check if sequence property is null or empty
    Ensures.NotNull(); //result object will not be null

    //Do stuff
}

ContractArgumentValidatorAttribute 我在教程外没有使用过这个,但基本上它允许您编写一个包含多个if (test) throw new ArgumentException()调用的单个调用,其行为类似于对Contract.Requires的调用。由于它只处理参数验证,它不会帮助您解决后置条件示例。


1
只是猜测,也许应该是 Contract.Ensures(result.Value >= 0)

嗯,结果实际上可能为空。 - Victor Rodrigues
2
那么,也许应该是(result == null || result >= 0) - Igor Zevaka
更一般地,蕴含关系 if x then y!x || y,因此在这里我们有 !(result != null) || result >= 0,它简化为 result == null || result >= 0 - porges
更一般而言,“如果x,则y”这个命题可以表示为“x.Implies(y)”,因此在这里我们有“(result != null).Implies(result >= 0)” - ctrl-alt-delor

-1

合约上有条件编译标志。 在发布模式下,您的代码

if condition
    contract
return

变成

if condition
   return

你现在看到问题了吗?


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