【问题标题】:'Design By Contract' in C#C# 中的“按合同设计”
【发布时间】:2008-11-04 03:50:30
【问题描述】:

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

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

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

【问题讨论】:

  • 你应该改变你接受的答案。

标签: c# design-by-contract


【解决方案1】:

C# 4.0 代码契约

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

以下是一些 Microsoft 资源:

这里有一些其他资源:

【讨论】:

  • Spec# 的问题在于它绑定到了 IDE。幸运的是,我使用的是 4.0,因为 System.Diagnostics.Contracts 正是 我正在寻找的。属性+代码。惊人的。这应该是答案。调试.断言?那不是 DbC。
  • 但是任何代码合同支持都将随着 .NET 5.0 消失:github.com/dotnet/docs/issues/6361
【解决方案2】:

Spec# 是一个流行的microsoft research project,它允许一些 DBC 构造,例如检查后置条件和前置条件。例如,可以使用前置条件和后置条件以及循环不变量来实现二进制搜索。 This example and more:

 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 的最佳方式。通常,依赖运行时断言会成为生产中令人头疼的问题,人们通常会选择use exceptions

other languages 将 DBC 概念作为一流的构造,即 Eiffel,它也可用于 .NET 平台。

【讨论】:

    【解决方案3】:

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

    using System.Diagnostics
    
    Debug.Assert(value != null);
    Debug.Assert(value == true);
    

    不是很有用,我知道。

    【讨论】:

    • 这将在发布版本中编译出来。
    • 是的,如果在 Debug 中。* 它们是从发布版本中编译出来的。
    • 这种方法的唯一问题是检查仍然在运行时完成。我知道编译器无法在编译时验证所有内容。但是假设你需要x != null,然后调用f(null),编译器可能会警告用户有问题。
    【解决方案4】:

    .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);
    

    【讨论】:

      【解决方案5】:

      Looking over the code for Moq I saw that they use a class called 'Guard' that provides static methods for checking pre and post conditions。我认为那很整洁,很清楚。它表达了我在代码中通过合同检查实现设计时的想法。

      例如

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

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

      【讨论】:

      • 我很抱歉我是一个死灵法师,但是如果你不想强迫每个人都安装 ccrewrite.exe 和所有那些爵士乐,那么 Moq 的 Guard 类就是完美的。
      【解决方案6】:

      您可能想查看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;,但它就是这样(幸运的是它是开源的,所以让它成为一个构建器是一个微不足道的改变)。

      作为替代解决方案,您可以consider domain validation

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

      【讨论】:

        【解决方案7】:

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

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

        简单大于复杂。

        【讨论】:

          【解决方案8】:

          最直接的方式,也是 .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;
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-09-29
            • 2012-12-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多