【问题标题】:Why does ReSharper suggest that I make type parameter T contravariant?为什么 ReSharper 建议我使类型参数 T 逆变?
【发布时间】:2015-04-25 13:59:42
【问题描述】:

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

interface IBusinessValidator<T> where T: IEntity
{
    void Validate(T entity);
}

进入这个:

interface IBusinessValidator<in T> where T: IEntity
{
    void Validate(T entity);
}

那么&lt;T&gt;&lt;in T&gt; 有什么不同呢?而这里逆变的目的是什么?

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

如何在这个例子中应用逆变的用法?

【问题讨论】:

标签: c# resharper contravariance


【解决方案1】:

那么有什么区别呢?

不同之处在于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&lt;User&gt;IBusinessValidator&lt;T&gt;。因为它是逆变的,我可以通过BusinessValidator&lt;Entity&gt;

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

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

【讨论】:

  • 是不是因为被声明为无效所以不建议?
  • void 与逆变有什么关系?之所以建议这样做,是因为他将T 传递给该方法。如果它使用它作为返回类型,它会建议out T
  • 所以在 T 内或在外 T 不是 OP 寻求答案的主要问题吗?在您的示例中,OP 将如何实现?
  • OP 询问了建议的目的
  • 我对逆变也有一点理解问题。所以我更新了这个问题。一个例子可能会有很大帮助。
【解决方案2】:

要了解 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&lt;Donkey&gt; 类型的变量或将其传递给某些方法的IGobbler&lt;Donkey&gt; 参数。这将是奇怪和不一致的;如果QuadrupedGobbler 可以狼吞虎咽,那它不就是一种驴狼吗?幸运的是,ReSharper 注意到了这种不一致,如果您在 IGobbler 声明中遗漏了 in,它会建议您添加它 - 并带有建议 “使类型参数 T 逆变” - 允许QuadrupedGobbler 用作 IGobbler&lt;Donkey&gt;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-21
    • 2012-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多