Search code examples
c#genericsinterfacecode-contracts

Should I use typed code contracts for generic interfaces?


I have a generic interface for a mathematics library, something like this:

[ContractClass(typeof(MathsDoubleContracts))]
public interface IMaths<T>
{
    T SomeFunction(T n);
}

This enables me to create an IMaths<double>, IMaths<decimal>, etc. (although at first I only need the double version).

I'd like to set up code contracts. At present I've done this:

[ContractClassFor(typeof(IMaths<double>))]
public abstract class MathsDoubleContracts : IMaths<double>
{
    public double SomeFunction(double n)
    {
        // Always positive
        Contract.Ensures(0 <= Contract.Result<double>());

        return double.NaN;
    }
}

This seems to work, but I'm rather surprised that it does (given that I'm specifying contracts on IMaths<double> rather than IMaths<T>).

I'm left wondering:

  1. Can I specify multiple contract classes on a generic interface, one for each specific type I want to use (e.g. have both [ContractClass(typeof(MathsDoubleContracts))] and [ContractClass(typeof(MathsDecimalContracts))] attributes on IMaths<T>)? Is this a sensible thing to do?
  2. Would I be better off not using generic interfaces at all (i.e. start with, say, IMathsDouble where all the functions are defined in terms of doubles, adding IMathsDecimal later)?

Solution

  • Q1

    Can you bring an example please.

    EDIT

    No. It does not allow multiple. Ref here.

    AttributeUsageAttribute(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    

    Note AllowMultiple = false.

    Q2

    Yes, although generics might provide some little benefit. Your interface is not really generic. I cannot use IMaths<File> for example. I actually answered a different but related question here.

    You may add restrictions such as where T : ... but that also will not do since you have to limit to only int, double while you can only do where T : struct which is not the same thing. Here generics is mere a decoration and the abstraction of IMaths<T> cannot be used as it is (or can it? can depend on your code) and you need concrete classes or interfaces.

    A sub-interface is a safe option here

    interface IntMaths : IMaths<int>
    {
    
    }