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
プロパティがあると仮定します。
この例で反変の使用法をどのように適用できますか?
では、<T>と<in T>の違いは何ですか?
違いはin T
を使用すると、指定したものよりも一般的な(派生が少ない)型を渡すことができます。
そして、ここで反変の目的は何ですか?
ReSharperは、T
パラメーターintoにValidate
メソッドを渡しており、汎用性を低くして、入力タイプを広げられるようにしたい。
一般に、反変は 反変の説明 と 共分散と反変の実世界の例 で説明され、もちろん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
キーワードを削除すると、次のエラーが発生します。
これを含めると、これは正常にコンパイルされます。
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());
ただし、QuadrupedGobbler
をIGobbler<Donkey>
型の変数に割り当てたり、メソッドのIGobbler<Donkey>
パラメータに渡したりすることはできません。これは奇妙で一貫性がないでしょう。もしQuadrupedGobbler
がロバをむさぼり食うことができるなら、それはそれを一種のロバのむさぼり者にしないのですか?幸いにも、ReSharperはこの不整合に気づき、in
宣言でIGobbler
を省略した場合、それを追加することを提案します-提案 "型パラメーターTを反変にする "-QuadrupedGobbler
をIGobbler<Donkey>
として使用できるようにします。
一般に、上で概説した同じロジックは、インターフェース宣言にメソッドのタイプparametersとしてのみ使用される汎用パラメーターが含まれる場合に適用されます。戻り値の型。