2013-08-24 47 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