使用代码约定来定义不可变接口?

时间:2012-02-04 00:03:24

标签: c# .net code-contracts

我可以使用代码约定来定义接口上的只读,不变属性吗?即实例化后总是产生相同值的属性?

2 个答案:

答案 0 :(得分:4)

首先注释.NET中的术语:

  • 只读:您碰巧拥有的界面不能用于改变对象或集合
  • 不可变:没有任何东西可以改变对象或集合

现在回到你的问题。

所有属性getter都在.NET Code Contracts中隐式标记为“Pure”。这意味着从吸气剂中读取应该永远不会产生明显的副作用。

严格来说,如果您的抽象接口只有只读属性,那么整个接口被认为是只读的。

但是,听起来你真正想要的是一种将接口标记为不可变的方法,并让底层类继承该状态。不幸的是,没有办法做到这一点,抽象接口只能添加功能。 Code Contracts可以做的最好的事情就是确保正确添加功能。

摘要

不,它不支持。

答案 1 :(得分:2)

这是一个可能的解决方案,作为概念证明。它有各种各样的问题,尤其是所有对象都将在缓存中保持活动状态,我们正在使用扩展方法有效地欺骗代码契约框架,以允许我们维护状态,但是它至少表明这种合同测试是可能的。

下面的代码定义了各种各样的东西:

  • 具有属性IRuntimeProperty的接口AlwaysTheSame,它返回一个整数。我们不关心价值是什么,但希望它总是返回相同的东西。
  • 一个静态类RuntimePropertyExtensions,用于定义扩展方法IsAlwaysTheSame,该方法使用缓存来自IRuntimeProperty个对象的先前结果
  • 一个类RuntimePropertyContracts,它调用扩展方法来检查AlwaysTheSame的返回值。
  • 以我们喜欢的方式实现GoodObject的类AlwaysTheSame,因此它始终为给定对象返回相同的值。
  • 以我们不喜欢的方式实现BadObject的类AlwaysTheSame,因此连续调用会返回不同的值。
  • 测试合同的Main方法。

以下是代码:

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace SameValueCodeContracts
{
    [ContractClass(typeof(RuntimePropertyContracts))]
    interface IRuntimeProperty
    {
        int AlwaysTheSame { get; }
    }

    internal static class RuntimePropertyExtensions
    {
        private static Dictionary<IRuntimeProperty, int> cache = new Dictionary<IRuntimeProperty, int>();

        internal static bool IsAlwaysTheSame(this IRuntimeProperty runtime, int newValue)
        {
            Console.WriteLine("in IsAlwaysTheSame for {0} with {1}", runtime, newValue);
            if (cache.ContainsKey(runtime))
            {
                bool result = cache[runtime] == newValue;
                if (!result)
                {
                    Console.WriteLine("*** expected {0} but got {1}", cache[runtime], newValue);
                }
                return result;
            }
            else
            {
                cache[runtime] = newValue;
                Console.WriteLine("cache now contains {0}", cache.Count);
                return true;
            }
        }
    }

    [ContractClassFor(typeof(IRuntimeProperty))]
    internal class RuntimePropertyContracts : IRuntimeProperty
    {
        public int AlwaysTheSame
        {
            get
            {
                Contract.Ensures(this.IsAlwaysTheSame(Contract.Result<int>()));
                return default(int);
            }
        }
    }

    internal class GoodObject : IRuntimeProperty
    {
        private readonly string name;
        private readonly int myConstantValue = (int)DateTime.Now.Ticks;

        public GoodObject(string name)
        {
            this.name = name;
            Console.WriteLine("{0} initialised with {1}", name, myConstantValue);
        }

        public int AlwaysTheSame
        {
            get
            {
                Console.WriteLine("{0} returning {1}", name, myConstantValue);
                return myConstantValue;
            }
        }
    }

    internal class BadObject : IRuntimeProperty
    {
        private readonly string name;
        private int myVaryingValue;

        public BadObject(string name)
        {
            this.name = name;
            Console.WriteLine("{0} initialised with {1}", name, myVaryingValue);
        }

        public int AlwaysTheSame
        {
            get
            {
                Console.WriteLine("{0} returning {1}", name, myVaryingValue);
                return myVaryingValue++;
            }
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            int value;

            GoodObject good1 = new GoodObject("good1");
            value = good1.AlwaysTheSame;
            value = good1.AlwaysTheSame;
            Console.WriteLine();

            GoodObject good2 = new GoodObject("good2");
            value = good2.AlwaysTheSame;
            value = good2.AlwaysTheSame;
            Console.WriteLine();

            BadObject bad1 = new BadObject("bad1");
            value = bad1.AlwaysTheSame;
            Console.WriteLine();

            BadObject bad2 = new BadObject("bad2");
            value = bad2.AlwaysTheSame;
            Console.WriteLine();

            try
            {
                value = bad1.AlwaysTheSame;
            }
            catch (Exception e)
            {
                Console.WriteLine("Last call caused an exception: {0}", e.Message);
            }
        }
    }
}

它输出如下:

good1 initialised with -2080305989
good1 returning -2080305989
in IsAlwaysTheSame for SameValueCodeContracts.GoodObject with -2080305989
cache now contains 1
good1 returning -2080305989
in IsAlwaysTheSame for SameValueCodeContracts.GoodObject with -2080305989

good2 initialised with -2080245985
good2 returning -2080245985
in IsAlwaysTheSame for SameValueCodeContracts.GoodObject with -2080245985
cache now contains 2
good2 returning -2080245985
in IsAlwaysTheSame for SameValueCodeContracts.GoodObject with -2080245985

bad1 initialised with 0
bad1 returning 0
in IsAlwaysTheSame for SameValueCodeContracts.BadObject with 0
cache now contains 3

bad2 initialised with 0
bad2 returning 0
in IsAlwaysTheSame for SameValueCodeContracts.BadObject with 0
cache now contains 4

bad1 returning 1
in IsAlwaysTheSame for SameValueCodeContracts.BadObject with 1
*** expected 0 but got 1
Last call caused an exception: Postcondition failed: this.IsAlwaysTheSame(Contract.Result())

我们可以根据需要创建尽可能多的GoodObject个实例。在他们身上调用AlwaysTheSame将始终满足合同。

相反,当我们创建BadObject个实例时,我们只能在每个上调用AlwaysTheSame ;一旦我们第二次调用它,合同就会抛出异常,因为返回的值与我们上次得到的值不一致。

正如我在开始时所说的那样,我对在生产代码中使用这种方法持谨慎态度。但我同意这是一个有用的东西,想通过契约来指定,如果在代码契约框架中内置了对这种运行时返回值不变性的支持,那就太棒了。