为什么ReSharper建议我使类型参数T逆变?

时间:2015-02-24 02:57:39

标签: c# resharper contravariance

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>之间的区别是什么?逆变的目的是什么?

假设我有IEntityEntityUserAccount个实体。假设UserAccount都有Name属性需要验证。

如何在此示例中应用逆变的用法?

2 个答案:

答案 0 :(得分:21)

  

那么&lt; T&gt;之间有什么不同呢?和&lt;在T&gt;?

不同之处在于in T允许您传递比指定类型更通用(更少派生)的类型。

  

这里逆变的目的是什么?

ReSharper建议在这里使用逆变,因为它会看到您将T参数传递给 Validate方法,并希望您能够通过以下方式扩展输入类型使它不那么通用。

一般情况下,在Contravariance explainedCovariance 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关键字,则会收到以下错误:

Not contravariant

如果我们加入它,这可以很好地编译。

答案 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>

通常,上面概述的相同逻辑适用于任何情况,其中接口声明包含仅用作方法类型参数的泛型参数,而不是返回类型。