Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Contravariance and operator overload

I have Specification pattern implementation and I wanted to change it to support contravariance. However interesting problem arose.

public interface ISpecification<in T>
{
    Func<T, bool> Predicate { get; }
    bool IsSatisfiedBy(T entity);
}

public class Specification<T> : ISpecification<T>
{
    public Specification(Func<T, bool> predicate)
    {
        this.Predicate = predicate;
    }

    public Func<T, bool> Predicate
    {
        get;
        private set;
    }

    public bool IsSatisfiedBy(T x)
    {
        return Predicate.Invoke(x);
    }

    public static Specification<T> operator &(Specification<T> left, ISpecification<T> right)
    {
        return new Specification<T>((x) => left.Predicate(x) && right.Predicate(x));
    }
}

this work as you can expect

new Specification<DerivedClass>((x) => true) & new Specification<BaseClass> ((x) => true)

but if I reverse the order of argument it doesn't compile anymore

new Specification<BaseClass>((x) => true) & new Specification<DerivedClass>((x) => true)

I understand why this happens but my question is - is there a way to have both working?

EDIT:

I already tried to define operator & with reverse order or params like that

public static Specification<T> operator &(ISpecification<T> left, Specification<T> right)
{
    return new Specification<T>((x) => left.Predicate(x) && right.Predicate(x));
}

but I am getting ambiguous call compiler error between both operators. I am using .NET 4.5

netfiddle: https://dotnetfiddle.net/GB66UN

like image 238
ekalchev Avatar asked Apr 21 '16 13:04

ekalchev


1 Answers

Yes -- just do it again for the other parameter order.

public static Specification<T> operator &(ISpecification<T> left, Specification<T> right)
{
    return new Specification<T>((x) => left.Predicate(x) && right.Predicate(x));
}

Operator overloading does not require that the first parameter be of the enclosing type, only that one of the parameters is.

AS @DStanley points out, even this will fail on a call of the form

new Specification<DerivedClass>((x) => true) & new Specification<DerivedClass>((x) => true);

So we do it again, for this particular combination of parameters:

public static Specification<T> operator &(Specification<T> left, Specification<T> right)
{
    return new Specification<T>((x) => left.Predicate(x) && right.Predicate(x));
}
like image 124
Jeroen Mostert Avatar answered Sep 30 '22 11:09

Jeroen Mostert