2015-02-24 4 views
24

ReSharper предлагает мне сделать параметр типа T контравариантного путем изменения этого:Почему 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>? И в чем же цель контравариантности здесь?

Пусть говорят, что я IEntity, Entity, User и Account сущности. Предполагая, что у User и Account есть Name свойство, которое необходимо проверить.

Как я могу применить использование контравариантного в этом примере?

+2

Вы уже прочитали документацию? https://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx –

+0

Да, я уже прочитал. Однако у меня есть небольшая проблема с пониманием статьи. Во всяком случае, ссылка помогла. –

ответ

16

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

Разница заключается в том, что in T позволяет передавать более общий (менее производным) типа, чем было указано.

And what is the purpose of contravariant here?

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); 
     } 
    } 
} 

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>. Потому что это contravariant, я могу передать его BusinessValidator<Entity>.

Если убрать in ключевое слово, мы получим следующее сообщение об ошибке:

Not contravariant

Если мы включаем его, это нормально компилируется.

+0

Разве это не означает, что он объявлен как пустота? – Amit

+0

Что не имеет отношения к контравариантности? Он предлагает это, потому что он передает 'T' в метод. Если бы он использовал его как возвращаемый тип, это предполагало бы 'out T' –

+0

Так что в T или вне T не является основным вопросом, на который OP ищет ответ? И в вашем примере, как OP это пойдет на это? – Amit

0

Чтобы понять мотивацию 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()); 

Если Марсело забыли использовать in ключевое слово в объявлении его интерфейса IGobbler, то система типа С # бы не признать его 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 декларации, то вам будет предложено добавить его - с предложением «Сделать параметр типа T контравариантного» - позволяя QuadrupedGobbler быть использованы в качестве IGobbler<Donkey>.

В общем, та же самая логика, изложенная выше, применяется в любом случае, когда декларация интерфейса содержит общий параметр, который используется только как тип метода параметров, а не типов возврата.

Смежные вопросы