Подтвердить что ты не робот

Почему 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, которое необходимо проверить.

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

4b9b3361

Ответ 1

Итак, что отличается между <T> и < в → <// >

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

И в чем же цель контравариантности здесь?

ReSharper предлагает использовать контравариантность здесь, потому что видит, что вы передаете параметр T в метод Validate и хотите, чтобы вы расширили тип ввода, сделав его менее общим.

В целом, контравариантность объясняется длиной в Контравариантность объясняется и в примере ковариации и контравариантности в реальном мире, и, конечно, во всей документации по MSDN (есть отличные ответы на часто задаваемые вопросы командой С#).

В 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

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

Ответ 2

Чтобы понять мотивацию ReSharper, рассмотрите осел-окунь Марсело Кантоса:

// 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 как осел-окунь, и поэтому это назначение из приведенного выше кода будет не удалось скомпилировать:

IGobbler<Donkey> dg = new QuadrupedGobbler();

Обратите внимание, что это не остановит QuadrupedGobbler от gobbling donkeys - например, следующий код будет работать:

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

Однако вы не сможете назначить QuadrupedGobbler переменной типа IGobbler<Donkey> или передать ее некоторому параметру IGobbler<Donkey>. Это было бы странно и непоследовательно; если QuadrupedGobbler может сожрать ослов, то разве это не делает это своего рода осел-сапожник? К счастью, ReSharper замечает эту несогласованность, и если вы не укажете объявление in в IGobbler, оно предложит вам добавить его - с предложением "Сделать параметр типа T контравариантным", что позволяет использовать QuadrupedGobbler как a IGobbler<Donkey>.

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