ReSharper建议我通过更改它来创建类型参数T contravariant:
interface IBusinessValidator<T> where T: IEntity
{
void Validate(T entity);
}
进入这个:
interface IBusinessValidator<in T> where T: IEntity
{
void Validate(T entity);
}
那么<T>
和<in T>
之间的区别是什么?逆变的目的是什么?
假设我有IEntity
,Entity
,User
和Account
个实体。假设User
和Account
都有Name
属性需要验证。
如何在此示例中应用逆变的用法?
答案 0 :(得分:21)
那么&lt; T&gt;之间有什么不同呢?和&lt;在T&gt;?
不同之处在于in T
允许您传递比指定类型更通用(更少派生)的类型。
这里逆变的目的是什么?
ReSharper建议在这里使用逆变,因为它会看到您将T
参数传递给 Validate
方法,并希望您能够通过以下方式扩展输入类型使它不那么通用。
一般情况下,在Contravariance explained和Covariance and contravariance real world example中解释逆变的长度,当然还有MSDN上的文档(有great FAQ by the C# team)。
通过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);
}
}
}
如何在此示例中应用逆变的用法?
假设我们有实体:
public class Entity : IEntity
{
public string Name { get; set; }
}
public class User : Entity
{
public string Password { get; set; }
}
我们还有IBusinessManager
接口和BusinessManager
实施,接受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.
}
}
现在,假设我们为任何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);
}
}
现在,我们希望传递BusinessManager<User>
和IBusinessValidator<T>
。因为它是逆变,我可以传递它BusinessValidator<Entity>
。
如果我们删除in
关键字,则会收到以下错误:
如果我们加入它,这可以很好地编译。
答案 1 :(得分:3)
要了解ReSharper的动机,请考虑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());
如果Marcelo忘记在in
界面的声明中使用IGobbler
关键字,那么C#的类型系统就不会将他的QuadrupedGobbler
识别为驴gobbler,所以从上面的代码中的这个分配将无法编译:
IGobbler<Donkey> dg = new QuadrupedGobbler();
请注意,这不会阻止QuadrupedGobbler
吞噬驴 - 例如,以下代码将工作:
IGobbler<Quadruped> qg = new QuadrupedGobbler();
qg.gobble(MyDonkey());
但是,您无法将QuadrupedGobbler
分配给IGobbler<Donkey>
类型的变量,或将其传递给某个方法的IGobbler<Donkey>
参数。这将是奇怪和不一致;如果QuadrupedGobbler
可以吞食驴子,那么它是不是会让它成为一种驴子?幸运的是,ReSharper注意到这种不一致性,如果你遗漏了in
声明中的IGobbler
,它会建议你添加它 - 建议&#34; Make type parameter T contravariant& #34; - 允许将QuadrupedGobbler
用作IGobbler<Donkey>
。
通常,上面概述的相同逻辑适用于任何情况,其中接口声明包含仅用作方法类型参数的泛型参数,而不是返回类型。