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

Vu *_*yen 35 c# resharper contravariance

ReSharper建议我通过改变这个来改变类型参数T contravariant:

interface IBusinessValidator<T> where T: IEntity
{
    void Validate(T entity);
}
Run Code Online (Sandbox Code Playgroud)

进入:

interface IBusinessValidator<in T> where T: IEntity
{
    void Validate(T entity);
}
Run Code Online (Sandbox Code Playgroud)

那么<T>和之间有什么不同<in T>?这里逆变的目的是什么?

让说我有IEntity,Entity,UserAccount实体.假设双方UserAccountName需要验证属性.

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

Yuv*_*kov 22

那么<T>和<T>之间的区别是什么?

区别在于in T允许您传递比指定类型更通用(更少派生)的类型.

这里逆变的目的是什么?

ReSharper的建议在这里使用逆变,因为它看到你传递的T参数Validate方法,并希望允许您通过使其不太通用拓宽输入类型.

一般来说,在逆变量解释协方差和逆变现实世界的例子中,逆向性被解释为长度,当然还有整个MSDN文档(C#团队有一个很棒的常见问题解答).

通过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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

假设我们有我们的实体:

public class Entity : IEntity
{
    public string Name { get; set; }
}

public class User : Entity
{
    public string Password { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我们还有一个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.
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,假设我们为任何创建了一个通用的验证器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);
    }
}
Run Code Online (Sandbox Code Playgroud)

而现在,我们希望通过BusinessManager<User>IBusinessValidator<T>.因为它是逆变的,我可以通过它BusinessValidator<Entity>.

如果我们删除in关键字,则会收到以下错误:

不是逆变的

如果我们包括它,这编译好.


Mar*_*ery 5

要理解 ReSharper 的动机,请考虑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());
Run Code Online (Sandbox Code Playgroud)

如果 Marcelo 忘记in在他的接口声明中使用关键字IGobbler,那么 C# 的类型系统将无法识别他QuadrupedGobbler为驴子吞噬者,因此上面代码中的赋值将无法编译:

IGobbler<Donkey> dg = new QuadrupedGobbler();
Run Code Online (Sandbox Code Playgroud)

请注意,这不会阻止QuadrupedGobbler驴子被吞噬 - 例如,以下代码可以工作:

IGobbler<Quadruped> qg = new QuadrupedGobbler();
qg.gobble(MyDonkey());
Run Code Online (Sandbox Code Playgroud)

但是,您无法将 a 分配QuadrupedGobbler给类型的变量IGobbler<Donkey>或将其传递给某些方法的IGobbler<Donkey>参数。这会很奇怪并且不一致;如果它能QuadrupedGobbler吃掉驴子,那它不是一种吃驴子的动物吗?in幸运的是,ReSharper 注意到了这种不一致,如果您在声明中省略了IGobbler,它会建议您添加它 - 并建议“使类型参数 T 逆变” - 允许QuadrupedGobbler将 a 用作IGobbler<Donkey>.

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