39

我想在我最新的 C# 应用程序中尝试一些按合同约定的设计,并希望语法类似于:

public string Foo()
{
    set {
        Assert.IsNotNull(value);
        Assert.IsTrue(value.Contains("bar"));
        _foo = value;
    }
}

我知道我可以从单元测试框架中获得这样的静态方法,但我想知道这样的东西是否已经内置到语言中,或者是否已经存在某种框架。我可以编写自己的 Assert 函数,只是不想重新发明轮子。

4

8 回答 8

86

C# 4.0 代码契约

Microsoft 在 .net 框架的 4.0 版中发布了一个按合同设计的库。该库最酷的功能之一是它还附带了一个静态分析工具(我猜类似于 FxCop),该工具利用了您放置在代码上的合约的详细信息。

以下是一些微软资源:

以下是一些其他资源:

于 2008-11-06T04:48:31.903 回答
23

Spec#是一个流行的微软研究项目,它允许一些 DBC 构造,例如检查后置条件和前置条件。例如,可以使用前置条件和后置条件以及循环不变量来实现二进制搜索。这个例子和更多:

 public static int BinarySearch(int[]! a, int key)
    requires forall{int i in (0: a.Length), int j in (i: a.Length); a[i] <= a[j]};
    ensures 0 <= result ==> a[result] == key;
    ensures result < 0 ==> forall{int i in (0: a.Length); a[i] != key};
 {
   int low = 0;
   int high = a.Length - 1;

   while (low <= high)
     invariant high+1 <= a.Length;
     invariant forall{int i in (0: low); a[i] != key};
     invariant forall{int i in (high+1: a.Length); a[i] != key};
   {
     int mid = (low + high) / 2;
     int midVal = a[mid];

     if (midVal < key) {
       low = mid + 1;
     } else if (key < midVal) {
       high = mid - 1;
     } else {
       return mid; // key found
     }
   }
   return -(low + 1);  // key not found.
 }

请注意,使用 Spec# 语言会产生对 DBC 结构的编译时检查,这对我来说是利用 DBC 的最佳方式。通常,依赖运行时断言成为生产中的一个令人头疼的问题,人们通常选择使用异常来代替。

还有其他语言将 DBC 概念作为一流的结构,即Eiffel,它也可用于 .NET 平台。

于 2008-11-04T03:55:50.420 回答
11

除了使用外部库之外,您在 System.Diagnostics 中有一个简单的断言:

using System.Diagnostics

Debug.Assert(value != null);
Debug.Assert(value == true);

不是很有用,我知道。

于 2008-11-04T03:54:48.580 回答
7

.net Fx 4.0 中有一个答案:

System.Diagnostics.Contracts

http://msdn.microsoft.com/en-us/library/dd264808.aspx

Contract.Requires(newNumber > 0, “Failed contract: negative”);
Contract.Ensures(list.Count == Contract.OldValue(list.Count) + 1);
于 2010-07-01T07:07:29.697 回答
2

查看 Moq 的代码,我看到他们使用了一个名为“Guard”的类,它提供了用于检查前置条件和后置条件的静态方法。我认为那很整洁,很清楚。它表达了我在代码中通过合同检查实现设计时的想法。

例如

public void Foo(Bar param)
{
   Guard.ArgumentNotNull(param);
} 

我认为这是通过合同检查来表达设计的一种巧妙方式。

于 2008-11-04T05:03:03.310 回答
1

您可能想查看nVentive Umbrella

using System;
using nVentive.Umbrella.Validation;
using nVentive.Umbrella.Extensions;

namespace Namespace
{
    public static class StringValidationExtensionPoint
    {
        public static string Contains(this ValidationExtensionPoint<string> vep, string value)
        {
            if (vep.ExtendedValue.IndexOf(value, StringComparison.InvariantCultureIgnoreCase) == -1)
                throw new ArgumentException(String.Format("Must contain '{0}'.", value));

            return vep.ExtendedValue;
        }
    }

    class Class
    {
        private string _foo;
        public string Foo
        {
            set
            {
                _foo = value.Validation()
                    .NotNull("Foo")
                    .Validation()
                    .Contains("bar");
            }
        }
    }
}

我希望验证扩展是构建器,所以你可以这样做_foo = value.Validation().NotNull("Foo").Contains("bar").Value;,但它就是这样(幸运的是它是开源的,所以让它成为一个构建器是一个微不足道的改变)。

作为替代解决方案,您可以考虑域验证

最后,作为 Oslo 的一部分,新的 M 语言支持对其范围和字段的限制,这些限制可以转换为 T-SQL 验证和具有功能验证测试的 CLR 类(尽管 Oslo 距离发布还有很长时间)。

于 2008-11-04T05:00:41.567 回答
1

对于我当前的项目(2010 年 2 月,VS 2008),我选择了http://lightcontracts.codeplex.com/

简单,它只是运行时验证,没有任何奇怪的复杂性,你不需要从一些“奇怪的”基类派生,没有 AOP,在某些开发人员工作站上不起作用的 VS 集成等。

简单大于复杂。

于 2010-02-26T01:25:40.597 回答
1

最直接的方法,以及 .NET Framework 本身使用的方法是:

public string Foo()
{
    set {
        if (value == null)
            throw new ArgumentNullException("value");
        if (!value.Contains("bar"))
            throw new ArgumentException(@"value should contain ""bar""", "value");

        _foo = value;
    }
}
于 2011-03-04T10:54:54.270 回答