c# - studio - resharper free
¿Por qué ReSharper sugiere que haga que el parámetro tipo T sea contravariante? (2)
ReSharper me sugiere hacer un parámetro de tipo T contravariante cambiando esto:
interface IBusinessValidator<T> where T: IEntity
{
void Validate(T entity);
}
Dentro de esto:
interface IBusinessValidator<in T> where T: IEntity
{
void Validate(T entity);
}
Entonces, ¿qué es diferente entre <T>
y <in T>
? ¿Y cuál es el propósito de la contravariante aquí?
Digamos que tengo IEntity
Entity
, Entity
, User
y Account
. Suponiendo que tanto el User
como la Account
tienen propiedades de Name
que deben validarse.
¿Cómo puedo aplicar el uso de contravariant en este ejemplo?
Entonces, ¿qué es diferente entre <T> y <en T>?
La diferencia es que in T
permite pasar un tipo más genérico (menos derivado) del que se especificó.
¿Y cuál es el propósito de la contravariante aquí?
ReSharper sugiere usar la contravarianza aquí porque ve que está pasando el parámetro T
al método Validate
y quiere permitirle ampliar el tipo de entrada haciéndolo menos genérico.
En general, la contravarianza se explica en mayor detalle, como se explica en la Contravarianza y en el ejemplo del mundo real de covarianza y contravarianza , y por supuesto a lo largo de toda la documentación en MSDN (hay un gran número de preguntas frecuentes del equipo de C # ).
Hay un buen ejemplo a través de 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);
}
}
}
¿Cómo puedo aplicar el uso de contravariant en este ejemplo?
Digamos que tenemos nuestras entidades:
public class Entity : IEntity
{
public string Name { get; set; }
}
public class User : Entity
{
public string Password { get; set; }
}
También tenemos una interfaz de IBusinessManager
y una implementación de BusinessManager
, que acepta un 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.
}
}
Ahora, digamos que creamos un validador genérico para cualquier 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);
}
}
Y ahora, queremos pasar BusinessManager<User>
e IBusinessValidator<T>
. Debido a que es contravariante , puedo pasarlo por BusinessValidator<Entity>
.
Si eliminamos la palabra clave in
, obtenemos el siguiente error:
Si lo incluimos, este compila bien.
Para entender la motivación de ReSharper, considere el comino de burros de Marcelo Cantos :
// 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());
Si Marcelo hubiera olvidado usar la palabra clave in en la declaración de su interfaz IGobbler
, entonces el sistema de tipo C # no reconocería a su QuadrupedGobbler
como un gobbler de burro, por lo que esta asignación del código anterior no se compilaría:
IGobbler<Donkey> dg = new QuadrupedGobbler();
Tenga en cuenta que esto no detendría a QuadrupedGobbler
de engullir burros; por ejemplo, el siguiente código funcionaría:
IGobbler<Quadruped> qg = new QuadrupedGobbler();
qg.gobble(MyDonkey());
Sin embargo, no podrá asignar un QuadrupedGobbler
a una variable de tipo IGobbler<Donkey>
o pasarlo a algún IGobbler<Donkey>
del IGobbler<Donkey>
. Esto sería extraño e inconsistente; Si el QuadrupedGobbler
puede engullir burros, ¿no es eso una especie de gobbler de burro? Afortunadamente, ReSharper advierte esta incoherencia, y si la IGobbler
en la declaración de IGobbler
, sugerirá que la agregue, con la sugerencia "Hacer que el tipo de parámetro T sea contravariante" , permitiendo que un QuadrupedGobbler
se use como un IGobbler<Donkey>
.
En general, la misma lógica descrita anteriormente se aplica en cualquier caso en el que una declaración de interfaz contenga un parámetro genérico que solo se use como el tipo de parámetros del método, no los tipos de retorno.