Vu Nguyen Vu Nguyen - 15 days ago 13
C# Question

Why does ReShaper suggest me to make type parameter T contravariant?

ReShaper suggests me to make type parameter T contravariant:

interface IBusinessValidator<T> where T: IEntity
{
void Validate(T entity);
}


Into this

interface IBusinessValidator<in T> where T: IEntity
{
void Validate(T entity);
}


So what is different between
<T>
and
<in T>
?

And what is the purpose of contravariant here?




Update: I already thought about asking another question but it is better to ask for the example in same question.

Let say I have
IEntity, Entity, User and Account
entity. Assuming that both
User
and
Account
have
Name
property that need to be validated.

How can I apply the usage of contravariant in this example?
(May be it is not a good example, so tell me if I need to correct anything)

Answer

So what is different between <T> and <in T>?

The difference is that in T allows you to pass a more generic (less derived) type than what was specified.

And what is the purpose of contravariant here?

ReSharper suggests to use contravariance here because it sees the you're passing the T parameter into the Validate method and wants to enable you to broaden the input type by making it less generic.

In general, contravariance is explained to length in Contravariance explained and in Covariance and contravariance real world example, and of course throughout the documentation on MSDN (there is a great FAQ by the C# team).

There is a nice example via MSDN:

abstract class Shape
{
    public virtual double Area { get { return 0; }}
}

class Circle : Shape
{
    private double r;
    public Circle(double radius) { r = radius; }
    public double Radius { get { return r; }}
    public override double Area { get { return Math.PI * r * r; }}
}

class ShapeAreaComparer : System.Collections.Generic.IComparer<Shape>
{
    int IComparer<Shape>.Compare(Shape a, Shape b) 
    { 
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : a.Area.CompareTo(b.Area);
    }
}

class Program
{
    static void Main()
    {
        // You can pass ShapeAreaComparer, which implements IComparer<Shape>, 
        // even though the constructor for SortedSet<Circle> expects  
        // IComparer<Circle>, because type parameter T of IComparer<T> is 
        // contravariant.
        SortedSet<Circle> circlesByArea = 
            new SortedSet<Circle>(new ShapeAreaComparer()) 
                { new Circle(7.2), new Circle(100), null, new Circle(.01) };

        foreach (Circle c in circlesByArea)
        {
            Console.WriteLine(c == null ? "null" : "Circle with area " + c.Area);
        }
    }
}

Edit:

How can I apply the usage of contravariant in this example?

Let's say we have our entities:

public class Entity : IEntity
{
    public string Name { get; set; }
}

public class User : Entity
{
    public string Password { get; set; }
}

We also have a IBusinessManager interface and a BusinessManager implementation, which accepts an IBusinessValidator:

public interface IBusinessManager<T>
{
    void ManagerStuff(T entityToManage);
}

public class BusinessManager<T> : IBusinessManager<T> where T : IEntity
{
    private readonly IBusinessValidator<T> validator;
    public BusinessManager(IBusinessValidator<T> validator)
    {
        this.validator = validator;
    }

    public void ManagerStuff(T entityToManage)
    {
        // stuff.
    }
}

Now, lets say we created a generic validator for any IEntity:

public class BusinessValidator<T> : IBusinessValidator<T> where T : IEntity
{
    public void Validate(T entity)
    {
        if (string.IsNullOrWhiteSpace(entity.Name))
            throw new ArgumentNullException(entity.Name);
    }
}

And now, we want to pass BusinessManager<User> and IBusinessValidator<T>. Because it is contravariant, i can pass it BusinessValidator<Entity>.

If we remove the in keyword, we get the following error:

Not contravariant

If we include it, this compiles fine.