web-dev-qa-db-ja.com

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>の違いは何ですか?そして、ここで反変の目的は何ですか?

IEntityEntityUserおよびAccountエンティティがあるとします。 UserAccountの両方に、検証が必要なNameプロパティがあると仮定します。

この例で反変の使用法をどのように適用できますか?

38
Vu Nguyen

では、<T>と<in T>の違いは何ですか?

違いはin Tを使用すると、指定したものよりも一般的な(派生が少ない)型を渡すことができます。

そして、ここで反変の目的は何ですか?

ReSharperは、TパラメーターintoValidateメソッドを渡しており、汎用性を低くして、入力タイプを広げられるようにしたい。

一般に、反変は 反変の説明共分散と反変の実世界の例 で説明され、もちろんMSDNのドキュメント全体に すばらしいFAQ )。

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>contravariantなので、渡すことができますBusinessValidator<Entity>

inキーワードを削除すると、次のエラーが発生します。

Not contravariant

これを含めると、これは正常にコンパイルされます。

22
Yuval Itzchakov

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

Marceloがinインターフェイスの宣言でIGobblerキーワードを使用するのを忘れていた場合、C#の型システムは彼のQuadrupedGobblerをロバのゴブラーとして認識しません。上記のコードからの割り当てはコンパイルに失敗します:

IGobbler<Donkey> dg = new QuadrupedGobbler();

これはQuadrupedGobblerがロバをむさぼることを止めないことに注意してください-たとえば、次のコードは機能します

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

ただし、QuadrupedGobblerIGobbler<Donkey>型の変数に割り当てたり、メソッドのIGobbler<Donkey>パラメータに渡したりすることはできません。これは奇妙で一貫性がないでしょう。もしQuadrupedGobblerがロバをむさぼり食うことができるなら、それはそれを一種のロバのむさぼり者にしないのですか?幸いにも、ReSharperはこの不整合に気づき、in宣言でIGobblerを省略した場合、それを追加することを提案します-提案 "型パラメーターTを反変にする "-QuadrupedGobblerIGobbler<Donkey>として使用できるようにします。

一般に、上で概説した同じロジックは、インターフェース宣言にメソッドのタイプparametersとしてのみ使用される汎用パラメーターが含まれる場合に適用されます。戻り値の型。

3
Mark Amery