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

Why does ReSharper suggest that I make type parameter T contravariant?

ReSharper 建议我通过更改以下内容使类型参数 T 逆变:

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 属性 需要验证。

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

So what is different between <T> and <in T>?

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

And what is the purpose of contravariant here?

ReSharper 建议在这里使用逆变,因为它看到您正在将 T 参数 传递给 Validate 方法,并希望您能够通过减少通用性来扩大输入类型。

一般情况下,在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).

中对长度进行了解释。

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?

假设我们有实体:

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 关键字,我们会收到以下错误:

如果我们包含它,编译会很好。

要了解 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 忘记在他的 IGobbler 接口的声明中使用 in 关键字,那么 C# 的类型系统就不会将他的 QuadrupedGobbler 识别为驴子火鸡,并且所以上面代码中的这个赋值将无法编译:

IGobbler<Donkey> dg = new QuadrupedGobbler();

请注意,这不会阻止 QuadrupedGobbler 狼吞虎咽 - 例如,以下代码 起作用:

IGobbler<Quadruped> qg = new QuadrupedGobbler();
qg.gobble(MyDonkey());

但是,您无法将 QuadrupedGobbler 分配给类型为 IGobbler<Donkey> 的变量或将其传递给某些方法的 IGobbler<Donkey> 参数。这将是奇怪和不一致的;如果 QuadrupedGobbler 可以吞驴,那它不就是一种吞驴者吗?幸运的是,ReSharper 注意到了这种不一致,如果您在 IGobbler 声明中遗漏了 in,它会建议您添加它 - 并建议 "Make type parameter T contravariant" - 允许 QuadrupedGobbler 用作 IGobbler<Donkey>.

一般来说,上面概述的相同逻辑适用于接口声明包含仅用作方法类型的通用参数的任何情况参数,而不是[=44] =]类型。