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?
A covariant type parameter is marked with the out keyword ( Out keyword in Visual Basic). You can use a covariant type parameter as the return value of a method that belongs to an interface, or as the return type of a delegate.
A type can be declared contravariant in a generic interface or delegate only if it defines the type of a method's parameters and not of a method's return type. In , ref , and out parameters must be invariant, meaning they are neither covariant nor contravariant.
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); } } }
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.
To understand ReSharper's motivation, consider Marcelo Cantos's donkey gobbler:
// Contravariance interface IGobbler<in T> { void gobble(T t); } // Since a QuadrupedGobbler can gobble any four-footed // creature, it is OK to treat it as a donkey gobbler. IGobbler<Donkey> dg = new QuadrupedGobbler(); dg.gobble(MyDonkey());
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With