2013-08-24 23 views
5

私はC#でジェネリッククラスを作成する方法を学ぼうとしています。誰かがなぜこのプログラムを実行するとコンパイルエラーが発生するのか説明できますか?C#generics with interfaces

私はIZooAnimalインターフェイスを作成しました。すべての動物園の動物がこのインターフェースを実装します。

public interface IZooAnimal 
{ 
    string Id { get; set; } 
} 

public class Lion : IZooAnimal 
{ 
    string Id { get; set; } 
} 

public class Zebra : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

ZooCageはケージ

public class Zoo 
{ 
    public IList<ZooCage<IZooAnimal>> ZooCages { get; set; } 
} 

I

class Program 
{ 
    static void Main(string[] args) 
    { 
     var lion = new Lion(); 
     var lionCage = new ZooCage<Lion>(); 
     lionCage.Animals = new List<Lion>(); 
     lionCage.Animals.Add(lion); 

     var zebra = new Zebra(); 
     var zebraCage = new ZooCage<Zebra>(); 
     zebraCage.Animals = new List<Zebra>(); 
     zebraCage.Animals.Add(zebra); 

     var zoo = new Zoo(); 
     zoo.ZooCages = new List<ZooCage<IZooAnimal>>(); 

     zoo.ZooCages.Add(lionCage); 
    } 
} 

クラスを使用するプログラムを持っている動物園のクラスと同じタイプ

public class ZooCage<T> where T : IZooAnimal 
{ 
    public IList<T> Animals { get; set; } 
} 

の動物を開催しますコンパイル私はfolloを取得する翼のエラー: エラー2引数1:「ConsoleApplication2.ZooCage<ConsoleApplication2.IZooAnimal>」から「ConsoleApplication2.ZooCage<ConsoleApplication2.Lion>」から変換することはできません

私は私のプログラムを実行させるために行うにはどのような変更がありますか?

+2

共分散と反動については、こちらをご覧ください(http://stackoverflow.com/q/2033912/644812)。 –

答えて

3

あなたはいないインタフェースを実装する具体的なタイプではなく、インタフェースを使用してリストを定義する必要があります

var lionCage = new ZooCage<IZooAnimal>(); 
    lionCage.Animals = new List<IZooAnimal>(); 

期待通り次に、あなたのコードが動作します。

具体的な型を一般化された型(@ default.kramerが指し示すようにcovariance and contravariance)に変換することが許可されていないため、初期コードが機能しませんでした。

// your ZooCage is still generic 
public class ZooCage<T> 
{ 
    // but you declare on creation which type you want to contain only! 
    private Type cageType = null; 
    public ZooCage(Type iMayContain) 
    { 
     cageType = iMayContain; 
     animals = new List<T>(); 
    } 
    // check on add if the types are compatible 
    public void Add(T animal) 
    { 
     if (animal.GetType() != cageType) 
     { 
      throw new Exception("Sorry - no matching types! I may contain only " + cageType.ToString()); 
     } 
     animals.Add(animal); 
    } 
    // should be generic but not visible to outher world! 
    private IList<T> animals { get; set; } 
} 

このコードは、あなたがすることができます::

私が思いついた解決策は、次のされ

var lion = new Lion(); 
    var lionCage = new ZooCage<IZooAnimal>(typeof(Lion)); 
    lionCage.Add(lion); 

    var zebra = new Zebra(); 
    var zebraCage = new ZooCage<IZooAnimal>(typeof(Zebra)); 
    zebraCage.Add(zebra); 

しかし、それは上のエラーがスローされます。今すぐ

zebraCage.Add(lion); 

動物園は安全に広げることができます。

+0

+1:うん、それは間違いなく署名が一致する問題です。 – code4life

+2

これはコンパイルエラーを防ぎますが、ジェネリックを使用する目的をすべて破ります。あなたがこれをやろうとするならば、ZooCageを一般的なものにしないでください。また、すべてのケージがどんなタイプの動物も許可するので、「ZooCageは同じタイプの動物を保持する」という要件を満たさない。 – JLRishe

+0

問題は、OPが望んでいることは、コンパイラがそうだというだけでなく、コンパイル時のタイプの安全性の検証がウィンドウ外に出るためです。コンパイラは、その構文が許可されていればコードが安全であることを確認することができないため、すべてのランタイムチェックに戻ります。だから、あなたがこれをどうやって投げるかは関係ありません。それは良い考えではないので不可能です。 –

2

複数のケージが必要ですが、ケージの種類ごとに1つの動物しか収容できないため、モデルはわずかにオフです。

次のように私は、コードを書き直し:

  • IZooAnimalは変更されません。
  • IZooAnimalの任意のタイプを受け入れる共変インターフェイスICageがあります。これにより、あらゆるタイプの動物に強く型付けされたケージを持つことができます。
  • 次に、Cage具体的な実装はICageです。 Cageは一般的ですが、簡単に抽象クラスにして、動物固有のケージ実装を行うこともできます。たとえば、ゼブラに草を餌にする必要があり、ライオンに肉を与える必要がある場合は、自分のケージの実装を専門にすることができます。

ここでは、完全なコードです:

public interface IZooAnimal 
{ 
    string Id { get; set; } 
} 

public interface ICage<out T> where T : IZooAnimal 
{ 
    IReadOnlyCollection<T> Animals { get; } 
} 

public class Cage<T> : ICage<T> where T: IZooAnimal 
{ 
    private readonly List<T> animals = new List<T>(); 

    public IReadOnlyCollection<T> Animals 
    { 
     get 
     { 
      return animals.AsReadOnly(); 
     } 
    } 

    public void CageAnimal(T animal) 
    { 
     animals.Add(animal); 
    } 
} 

public class Lion : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public class Zebra : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public class Zoo 
{ 
    public IList<ICage<IZooAnimal>> Cages { get; set; } 
} 

internal class Program 
{ 

    private static void Main(string[] args) 
    { 
     var lion = new Lion(); 
     var zebra = new Zebra(); 
     var lionCage = new Cage<Lion>(); 
     lionCage.CageAnimal(lion); 

     var zebraCage = new Cage<Zebra>(); 
     zebraCage.CageAnimal(zebra); 

     var zoo = new Zoo(); 
     zoo.Cages.Add(lionCage); 
     zoo.Cages.Add(zebraCage); 

    } 
} 
3

はDanielMannの答え@かなり良いですが、1つの欠点を有する:オリジナルIListインタフェースはICageインターフェイスで使用することはできません。代わりに、ICageはReadOnlyCollectionを公開し、CageAnimalという新しいメソッドを公開する必要があります。

私は同様のアプローチでコードを書き直しました。私のICageの実装ははるかに弱いですが、内部でIListセマンティクスを使うことができます。

public interface IZooAnimal 
{ 
    string Id { get; set; } 
} 

public class Lion : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public class Zebra : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public interface ICage 
{ 
    IEnumerable<IZooAnimal> WeaklyTypedAnimals { get; } 
} 

public class Cage<T> : ICage where T : IZooAnimal 
{ 
    public IList<T> Animals { get; set; } 

    public IEnumerable<IZooAnimal> WeaklyTypedAnimals 
    { 
     get { return (IEnumerable<IZooAnimal>) Animals; } 
    } 
} 

public class Zoo 
{ 
    public IList<ICage> ZooCages { get; set; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var lion = new Lion(); 
     var lionCage = new Cage<Lion>(); 
     lionCage.Animals = new List<Lion>(); 
     lionCage.Animals.Add(lion); 

     var zebra = new Zebra(); 
     var zebraCage = new Cage<Zebra>(); 
     zebraCage.Animals = new List<Zebra>(); 
     zebraCage.Animals.Add(zebra); 

     var zoo = new Zoo(); 
     zoo.ZooCages = new List<ICage>(); 

     zoo.ZooCages.Add(lionCage); 
    } 
} 
+0

優れたポイント。私はあなたのアプローチを考えました! :) –

+0

ライオンを肌に触れるだけの方法はたくさんあります。 :) – CSJ

+0

確かに - 私よりもはるかにエレガントで清潔なソリューション! :) – pasty