2013-07-02 3 views
24

私は議題の予定を操作するためのインタフェースおよび一般的なクラスのカップルを作成している:私は唯一の有効なタイプを指定することができることを確実にするために型パラメータにいくつかの制約を使用しようとしているはなぜジェネリック型の制約はありません暗黙の基準変換エラーが発生していますか?

interface IAppointment<T> where T : IAppointmentProperties 
{ 
    T Properties { get; set; } 
} 

interface IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties> 
{ 
    DateTime Date { get; set; } 
    T Appointment { get; set; } 
} 

interface IAppointmentProperties 
{ 
    string Description { get; set; } 
} 

class Appointment<T> : IAppointment<T> where T : IAppointmentProperties 
{ 
    public T Properties { get; set; } 
} 

class AppointmentEntry<T> : IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties> 
{ 
    public DateTime Date { get; set; } 
    public T Appointment { get; set; } 
} 

class AppointmentProperties : IAppointmentProperties 
{ 
    public string Description { get; set; } 
} 

TIAppointment<IAppointmentProperties>を実装する必要があることを定義する制約を指定する際にAppointment<AppointmentProperties>あるクラスを使用した場合しかし、コンパイラはエラーを与える:

class MyAppointment : Appointment<MyAppointmentProperties> 
{ 
} 

// This goes wrong: 
class MyAppointmentEntry : AppointmentEntry<MyAppointment> 
{ 
} 

class MyAppointmentProperties : AppointmentProperties 
{ 
    public string ExtraInformation { get; set; } 
} 

エラーは次のとおりです。

The type 'Example.MyAppointment' cannot be used as type parameter 'T' in the generic type or method 'Example.AppointmentEntry<T>'. There is no implicit reference conversion from 'Example.MyAppointment' to 'Example.IAppointment<Example.IAppointmentProperties>'.

誰もが理由を説明でしたこれは動作しません?

+5

これは奇妙です。 **しかし**:これはジェネリック医薬品の大量の過度使用です。非常に単純化されたコード(私はそうであると思われる)をほとんど読めません。 –

答えて

91

はのは、単純化してみましょう:

interface IAnimal { ... } 
interface ICage<T> where T : IAnimal { void Enclose(T animal); } 
class Tiger : IAnimal { ... } 
class Fish : IAnimal { ... } 
class Cage<T> : ICage<T> where T : IAnimal { ... } 
ICage<IAnimal> cage = new Cage<Tiger>(); 

あなたの質問は次のとおりです。なぜ最後の行は違法でしょうか?

今、私はそれを簡素化するためのコードを書き換えていること、それは明らかです。 ICage<IAnimal>は、任意の動物を置くことができるケージですが、Cage<Tiger>はホッキョクグマしか持たないだから、これは違法でなければなりません。

cage.Enclose(new Fish()); 

をそしてちょっと、あなただけの虎ケージに魚を置く:それは違法でなかった場合

あなたはこれを行うことができます。

タイプシステムでは、ソースタイプのケイパビリティが未満であるというルールに違反するため、ターゲットタイプのケイパビリティよりもその変換が許可されません。 (これが有名な「リスコフの置換原則」の形である。)

具体的には、私はあなたがジェネリックを乱用していると言うでしょう。あなた自身を分析することのためにあなたがあまりにも複雑であるタイプの関係を作ったという事実は、あなたが全体を簡素化するべきであるという証拠です。あなたがすべてのタイプの関係を真っ直ぐに保っておらず、あなたがその事を書いたなら、あなたのユーザーは確かにそれをまっすぐに保つことができません。あなたがからのサンプルインターフェース再定義する場合、それは動作します

+0

あなたの簡素化により、より明確になります!クラスを再設計して、より洗練された方法でジェネリックを使用しようとします。答えとしてマーク。 – Rens

+1

これは非常に良い/簡単な説明です。私はJon Skeetの本C#をよく読んで覚えています。それはC#のジェネリック型共分散反共分散をとてもうまく説明しています。強くお勧めします。 –

+0

"虎のケージに魚を入れるだけです。" これはトラの魚ですか? :-) – twelveFunKeys

6

あなたは具体的なタイプではなく、インタフェースを使用してMyAppointmentクラスを宣言しているため。

class MyAppointment : Appointment<IAppointmentProperties> { 
} 

これで、変換が暗黙的に行われる可能性があります。制約where T: IAppointment<IAppointmentProperties>あなたがAppointmentEntry<T>ため、不特定のタイプはIAppointmentPropertiesと宣言されている任意の型に対応しなければならないことにより、契約を作成しているとAppointmentEntry<T>を宣言することで

。具象クラスとタイプを宣言することによって、あなたはその契約に違反している(それはIAppointmentPropertiesタイプではなく、あらゆる型を実装します)。

+0

はい、あなたは正しいです。しかし、具体的な型 'MyAppointmentProperties'を使って' MyAppointment'クラスを宣言したいと思います(これまでの例では私の謝罪ではありませんでした)。それを可能にする契約を指定することは可能ですか? – Rens

+0

'class AppointmentEntry :IAppointmentEntry ここで、TAppointment:IAppointment 'しかし、私はあなたのタイプ階層を過密にしないように注意します。そのような非常に説得力のある理由がない限り。 –

+0

説明をありがとうございます。タイプ階層をあまり複雑にしないように見ていきます。 – Rens

0

interface ICage<out T> 

interface ICage<T> 

を(outキーワードを何卒ご了承下さい)

次の文は正しいです。

ICage<IAnimal> cage = new Cage<Tiger>(); 
6

エリックはすでに非常に良い答えがあります。 不変,共分散反対称については、この機会にこのチャンスを取りたいと思っていました。定義について

の動物園があるとしましょうhttps://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx


を参照してください。

abstract class Animal{} 
abstract class Bird : Animal{} 
abstract class Fish : Animal{} 
class Dove : Bird{} 
class Shark : Fish{} 

動物園は再配置されているため、動物は古い動物園から新しい動物園に移動する必要があります。

不変

我々はそれらを移動する前に、我々は別の容器に動物を配置する必要があります。容器はすべて同じ操作をします:動物を入れたり、動物を動物から出したりします。

interface IContainer<T> where T : Animal 
{ 
    void Put(T t); 
    T Get(int id); 
} 

明らかに魚のために、我々は、タンクを必要とする:

class FishTank<T> : IContainer<T> where T : Fish 
{ 
    public void Put(T t){} 
    public T Get(int id){return default(T);} 
} 

ので、魚がに入れてタンクから出ることができます(うまくいけば、まだ生きている):

IContainer<Fish> fishTank = new FishTank<Fish>(); //Invariance, the two types have to be the same 
fishTank.Put(new Shark());   
var fish = fishTank.Get(8); 

と仮定我々 を許可するとに変更するとIContainer<Animal>になります。誤ってタンクに鳩を入れると、悲惨な悲劇が起こります。

IContainer<Animal> fishTank = new FishTank<Fish>(); //Wrong, some animal can be killed 
fishTank.Put(new Shark()); 
fishTank.Put(new Dove()); //Dove will be killed 

Contravariance

(管理は常にこの操作を行う)の負荷を分離し、プロセスをアンロードするために、動物園管理チームのdicidesをeffeciencyを改善するために。つまり、ロード専用とアンロードの2つの操作があります。私たちは動物をアンロードするためのチームを持って新しい動物園で

class BirdCage<T> : ILoad<T> where T : Bird 
{ 
    public void Put(T t) 
    { 
    } 
} 

ILoad<Bird> normalCage = new BirdCage<Bird>(); 
normalCage.Put(new Dove()); //accepts any type of birds 

ILoad<Dove> doveCage = new BirdCage<Bird>();//Contravariance, Bird is less specific then Dove 
doveCage.Put(new Dove()); //only accepts doves 

共分散

ザ・:

interface ILoad<in T> where T : Animal 
{ 
    void Put(T t); 
} 

はその後、我々は鳥かごを持っています。

interface IUnload<out T> where T : Animal 
{ 
    IEnumerable<T> GetAll(); 
} 

class UnloadTeam<T> : IUnload<T> where T : Animal 
{ 
    public IEnumerable<T> GetAll() 
    { 
     return Enumerable.Empty<T>(); 
    } 
} 

IUnload<Animal> unloadTeam = new UnloadTeam<Bird>();//Covariance, since Bird is more specific then Animal 
var animals = unloadTeam.GetAll(); 

チームの視点から見ると、内部に何が入っているかは関係ありません。動物の容器から動物を降ろすだけです。

関連する問題