组合接口的代码协定

本文关键字:代码 接口 组合 | 更新日期: 2023-09-27 18:32:50

我正在使用Microsoft代码合约一段时间了,今天我偶然发现了一个尴尬的案例。我的问题是 - 有没有一种优雅的方法来解决这种情况?

假设我有一个仓库的简单特征接口,如下所示:

[ContractClass(typeof(CanAddContract)]
public interface ICanAdd {
    void Add(object entity);
}

此属性中的约定如下所示:

[ContractClassFor(typeof(ICanAdd))]
internal abstract class CanAddContract {
    public void Add(object entity) {
        Contract.Requires(object != null); // guard against null argument
    }
}

所以,现在我们有类似的实体删除特征

[ContractClass(typeof(CanDeleteContract))]
public interface ICanDelete {
    void Delete(object entity);
}

。和合同...

[ContractClassFor(typeof(ICanDelete))]
internal abstract class CanDeleteContract {
    public void Delete(object entity) {
        Contract.Requires(entity != null); // guard against null argument
    }
}

这没有错。但是由于接口表示存储库特征,因此它们用于组成存储库接口:

public interface IEntityStore : ICanAdd, ICanDelete {
    void SomeOtherMethodThatNeedsAContract();
}

现在怎么办?当我想为此接口创建合约类时,我必须再次重新实现上述两个合约类,因为 C# 中不允许多重继承。这让我处于必须复制合约代码的情况。想想看 - 这对我来说似乎在每种可能的情况下都是错误的。

我能做些什么呢?

组合接口的代码协定

CodeContracts编译时重写器将自动发现并使用所有基接口的协定。

对于您的特定示例(请注意您不需要重复任何基本接口的合约,但它们仍然有效(:

using System;
using System.Diagnostics.Contracts;
namespace Demo
{
    [ContractClass(typeof(CanAddContract))]
    public interface ICanAdd
    {
        void Add(object entity);
    }
    [ContractClassFor(typeof (ICanAdd))]
    internal abstract class CanAddContract: ICanAdd
    {
        public void Add(object entity)
        {
            Contract.Requires(entity != null);
        }
    }
    [ContractClass(typeof(CanDeleteContract))]
    public interface ICanDelete
    {
        void Delete(object entity);
    }
    [ContractClassFor(typeof(ICanDelete))]
    internal abstract class CanDeleteContract: ICanDelete
    {
        public void Delete(object entity)
        {
            Contract.Requires(entity != null);
        }
    }
    [ContractClass(typeof(EntityStoreContract))]
    public interface IEntityStore: ICanAdd, ICanDelete
    {
        void SomeOtherMethodThatNeedsAContract(object entity);
    }
    // Note how we only specify the additional contract for SomeOtherMethodThatNeedsAContract().
    // We do NOT need to repeat the contracts for ICanAdd and ICanDelete.
    // These contracts are automatically inferred from the ICanAdd and ICanDelete contracts.
    [ContractClassFor(typeof(IEntityStore))]
    internal abstract class EntityStoreContract: IEntityStore
    {
        public void SomeOtherMethodThatNeedsAContract(object entity)
        {
            Contract.Requires(entity != null);
        }
        public abstract void Add(object entity);
        public abstract void Delete(object entity);
    }
    public sealed class EntityStore: IEntityStore
    {
        public void Add(object entity)
        {
        }
        public void Delete(object entity)
        {
        }
        public void SomeOtherMethodThatNeedsAContract(object entity)
        {
        }
    }
    public static class Program
    {
        private static void Main()
        {
            var entityStore = new EntityStore();
            entityStore.Add(null); // This will correctly give a code contracts exception.
        }
    }
}