Why does ReSharper suggest that I make type parameter T contravariant?
C#ResharperContravarianceC# Problem Overview
ReSharper suggests me to make type parameter T contravariant by changing this:
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?
Let say I have IEntity
, Entity
, User
and Account
entities. 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?
C# Solutions
Solution 1 - C#
> 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 https://stackoverflow.com/questions/1962629/contravariance-explained and in https://stackoverflow.com/questions/2662369/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);
}
}
}
> 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>
an IBusinessValidator<T>
. Because it is contravariant, I can pass it BusinessValidator<Entity>
.
If we remove the in
keyword, we get the following error:
If we include it, this compiles fine.
Solution 2 - C#
To understand ReSharper's motivation, consider Marcelo Cantos's donkey gobbler:
> // Contravariance
> interface IGobbler
> // Since a QuadrupedGobbler can gobble any four-footed
> // creature, it is OK to treat it as a donkey gobbler.
> IGobbler
If Marcelo had forgotten to use the in
keyword in the declaration of his IGobbler
interface, then C#'s type system wouldn't recognise his QuadrupedGobbler
as a donkey gobbler, and so this assignment from the code above would fail to compile:
IGobbler<Donkey> dg = new QuadrupedGobbler();
Note that this wouldn't stop the QuadrupedGobbler
from gobbling donkeys - for instance, the following code would work:
IGobbler<Quadruped> qg = new QuadrupedGobbler();
qg.gobble(MyDonkey());
However, you wouldn't be able to assign a QuadrupedGobbler
to a variable of type IGobbler<Donkey>
or pass it to some method's IGobbler<Donkey>
parameter. This would be weird and inconsistent; if the QuadrupedGobbler
can gobble donkeys, then doesn't that make it a kind of donkey gobbler? Luckily, ReSharper notices this inconsistency, and if you leave out the in
in the IGobbler
declaration, it will suggest that you add it - with the suggestion "Make type parameter T contravariant" - allowing a QuadrupedGobbler
to be used as an IGobbler<Donkey>
.
In general, the same logic outlined above applies in any case where an interface declaration contains a generic parameter that is only used as the type of method parameters, not return types.