Code Contracts for composed interfaces

185 Views Asked by At

I'm working with Microsoft Code Contracts for a little while now and today I stumbled upon an awkward case. My question is - is there an elegant way to resolve this situation?

Let's assume I've got a simple trait interface for a repository which looks like this:

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

The contract for this, as denoted in the attribute, looks like this:

[ContractClassFor(typeof(ICanAdd))]
internal abstract class CanAddContract {

    public void Add(object entity) {
        Contract.Requires(object != null); // guard against null argument
    }

}

So, now we've got a similar trait for entity deletion

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

...and the contract...

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

Nothing wrong about that. But since the interfaces denote repository traits, they are being used to compose a repository interface:

public interface IEntityStore : ICanAdd, ICanDelete {

    void SomeOtherMethodThatNeedsAContract();

}

Now what? When I want to create a contract class for this interface, I have to reimplement both contract classes stated above again, since multi-inheritance isn't allowed in C#. This leaves me in a situation where I have to duplicate code for a CONTRACT. Think about that - this seems wrong to me in every possible case.

What could I do about it?

1

There are 1 best solutions below

0
On BEST ANSWER

The CodeContracts compile-time rewriter will automatically discover and use the contracts for all base interfaces.

For your specific example (note how you do NOT need to repeat any of the base interfaces' contracts, and yet they still work):

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.
        }
    }
}