2013-08-24 175 views
5

我想學習如何用c#創建泛型類。有人可以解釋爲什麼我運行這個程序時出現編譯錯誤。C#泛型與接口

我創建了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 ZooCage<T> where T : IZooAnimal 
{ 
    public IList<T> Animals { get; set; } 
} 

動物園類的動物有籠子

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

使用類節目

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

當我編譯我得到以下結果翼錯誤: 錯誤2參數1:無法從「ConsoleApplication2.ZooCage<ConsoleApplication2.Lion>」轉換爲「ConsoleApplication2.ZooCage<ConsoleApplication2.IZooAnimal>

我有什麼變化,以使我的程序運行呢?

+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

由於您想擁有多個籠子,但每種籠子只能容納一隻動物,因此您的模型略有偏差。

我重寫了代碼如下:

  • IZooAnimal不變。
  • 有一個協變接口01​​,它接受任何類型的IZooAnimal。這可以讓你爲每種類型的動物製作一個牢固的籠子。
  • 然後,我有一個CageICage的具體實現。 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的答案是相當不錯的,但是從一個缺陷::原來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