“合同设计” 在 C# 中

发布于 2024-07-09 00:54:19 字数 309 浏览 13 评论 0原文

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

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

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

I wanted to try a little design by contract in my latest C# application and wanted to have syntax akin to:

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

I know I can get static methods like this from a unit test framework, but I wanted to know if something like this was already built-in to the language or if there was already some kind of framework floating around. I can write my own Assert functions, just don't want to reinvent the wheel.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

清浅ˋ旧时光 2024-07-16 00:54:19

C# 4.0 代码契约

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

以下是一些 Microsoft 资源:

以下是一些其他资源:

C# 4.0 Code Contracts

Microsoft has released a library for design by contract in version 4.0 of the .net framework. One of the coolest features of that library is that it also comes with a static analysis tools (similar to FxCop I guess) that leverages the details of the contracts you place on the code.

Here are some Microsoft resources:

Here are some other resources:

旧瑾黎汐 2024-07-16 00:54:19

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# 语言会产生编译时检查 em> 对于 DBC 构造,对我来说,这是利用 DBC 的最佳方式。 通常,依赖运行时断言会成为生产中的一个难题,人们通常选择 使用异常来代替。

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

Spec# is a popular microsoft research project that allows for some DBC constructs, like checking post and pre conditions. For example a binary search can be implemented with pre and post conditions along with loop invariants. 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.
 }

Note that using the Spec# language yields compile time checking for DBC constructs, which to me, is the best way to take advantage of DBC. Often, relying on runtime assertions becomes a headache in production and people generally elect to use exceptions instead.

There are other languages that embrace DBC concepts as first class constructs, namely Eiffel which is also available for the .NET platform.

决绝 2024-07-16 00:54:19

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

using System.Diagnostics

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

我知道,不是很有用。

Aside from using an external library, you have a simple assert in System.Diagnostics:

using System.Diagnostics

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

Not very useful, I know.

十雾 2024-07-16 00:54:19

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

There has an answer in .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);
抽个烟儿 2024-07-16 00:54:19

查看 Moq 的代码,我发现他们使用名为“Guard”的类提供用于检查前置条件和后置条件的静态方法。 我认为这很简洁而且非常清晰。 它表达了我在代码中通过契约检查实现设计时的想法。

例如,

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

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

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. I thought that was neat and very clear. It expresses what I'd be thinking about when implementing design by contract checks in my code.

e.g.

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

I thought it was a neat way to express design by contract checks.

三生池水覆流年 2024-07-16 00:54:19

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

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

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

You may want to check out 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");
            }
        }
    }
}

I wish the Validation extensions were builders so you could do _foo = value.Validation().NotNull("Foo").Contains("bar").Value; but it is what it is (fortunately its open source so making it a builder is a trivial change).

And as an alternative solution you could consider domain validation.

Finally the new M languages, as part of Oslo, support restrictions on their extents and fields which translate both to T-SQL validation and a CLR class with functioning validation tests (though Oslo is a long time off from release).

2024-07-16 00:54:19

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

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

简单胜于复杂。

For my current project (february 2010, VS 2008) I've choose http://lightcontracts.codeplex.com/

Simple, it's just runtime validation, without any weird complexity, you don't need to derive from some 'strange' base classes, no AOP, VS integration which won't work on some developer workstations, etc.

Simplicity over complexity.

痴梦一场 2024-07-16 00:54:19

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

The most straightforward way, and the way used in the .NET Framework itself, is to do:

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;
    }
}
明月松间行 2024-07-16 00:54:19

请注意,我在 C# 中为 DbC 创建了一个非常简单的类,它应该可以在 .NET 6 和我相信的任何 .NET 中工作,它非常简单且有限,但它可以达到使用前置条件、后置条件和断言的目的。

就在这里

namespace System.Diagnostics.Meyer.Contracts
{
    public static class Contract
    {
        private static void Initialize()
        {
            System.Diagnostics.Trace.Listeners.Clear();
            DefaultTraceListener defaultListener;
            defaultListener = new DefaultTraceListener();
            Trace.Listeners.Add(defaultListener);
            defaultListener.LogFileName = @".\Logs\contract.log";
        }

        static Contract()
        {
            Initialize();
        }

        public static void Assert(bool condition, string message = "")
        {
            System.Diagnostics.Trace.Assert(condition, "Assertion violation:", message);
        }

        public static void Require(bool condition, string message = "")
        {
            System.Diagnostics.Trace.Assert(condition, "Precondition violation:", message);
        }

        public static void Ensure(bool condition, string message = "")
        {
            System.Diagnostics.Trace.Assert(condition, "Postcondition violation:", message);
        }
    }
}

,用法可以是类似

        public void Open(LoggerLevel level, string version)
        {
            Contract.Require(version != null, "version != null");
            Contract.Require(_open == false, "_open == false");
            // ...
            _open = true;
            Contract.Ensure(_open == true, "_open == true");
        }

or

        public LoggerLevel Level
        {
            get
            {
                return _level;
            }
            set
            {
                Contract.Require(_open == true, "_open == true");
                if (value != _level)
                {
                    _level = value;
                    if (Level != LoggerLevel.Off)
                    { 
                        WriteContent(GetLevelChangeContent());
                    }
                }
            }
        }

    public class Program
    {
        private static Utility _utility = new Utility();

        public static void Main(string[] args)
        {
            Utility utility = _utility;
            Contract.Assert(utility != null, "utility != null");

​​。

Please note that I created an extremely simple class for DbC in C#, it should work in .NET 6, and any .NET I believe, it is very simple and limited, but it can serve the purpose of using preconditions, postconditions and assertions.

Here it is

namespace System.Diagnostics.Meyer.Contracts
{
    public static class Contract
    {
        private static void Initialize()
        {
            System.Diagnostics.Trace.Listeners.Clear();
            DefaultTraceListener defaultListener;
            defaultListener = new DefaultTraceListener();
            Trace.Listeners.Add(defaultListener);
            defaultListener.LogFileName = @".\Logs\contract.log";
        }

        static Contract()
        {
            Initialize();
        }

        public static void Assert(bool condition, string message = "")
        {
            System.Diagnostics.Trace.Assert(condition, "Assertion violation:", message);
        }

        public static void Require(bool condition, string message = "")
        {
            System.Diagnostics.Trace.Assert(condition, "Precondition violation:", message);
        }

        public static void Ensure(bool condition, string message = "")
        {
            System.Diagnostics.Trace.Assert(condition, "Postcondition violation:", message);
        }
    }
}

and the usage can go something like

        public void Open(LoggerLevel level, string version)
        {
            Contract.Require(version != null, "version != null");
            Contract.Require(_open == false, "_open == false");
            // ...
            _open = true;
            Contract.Ensure(_open == true, "_open == true");
        }

or

        public LoggerLevel Level
        {
            get
            {
                return _level;
            }
            set
            {
                Contract.Require(_open == true, "_open == true");
                if (value != _level)
                {
                    _level = value;
                    if (Level != LoggerLevel.Off)
                    { 
                        WriteContent(GetLevelChangeContent());
                    }
                }
            }
        }

or

    public class Program
    {
        private static Utility _utility = new Utility();

        public static void Main(string[] args)
        {
            Utility utility = _utility;
            Contract.Assert(utility != null, "utility != null");

etc.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文