C# 泛型与接口

5

我正在尝试学习如何使用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; }
}

动物园笼子将容纳相同类型的动物。
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);
    }
}

当我编译时,出现以下错误:

Error 2 Argument 1: cannot convert from 'ConsoleApplication2.ZooCage<ConsoleApplication2.Lion>' to 'ConsoleApplication2.ZooCage<ConsoleApplication2.IZooAnimal>'

我需要做哪些更改才能使程序运行?


2
了解协变和逆变...也许可以从这里开始阅读:https://dev59.com/xHI-5IYBdhLWcg3wED5V? - default.kramer
3个回答

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

确实,比我的解决方案更优雅、更简洁!:) - keenthinker

2

在定义列表时,应该使用实现接口的具体类型而不是具体类型本身:

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

那么你的代码将按预期工作。

初始代码无法正常工作,因为不允许将具体类型转换为广义类型(如@default.kramer指出的协变和逆变)。

我想到的解决方法如下:

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

现在动物园可以安全地扩建了。

2
这样做可以避免编译错误,但它违背了使用泛型的初衷。如果你要这样做,还不如不将ZooCage设为泛型。而且它也无法满足“ZooCage将容纳相同类型的动物”的要求,因为所有的笼子都允许任何类型的动物进入。 - JLRishe
问题在于,OP想要的不可能实现,这不仅是因为编译器的限制,而且因为编译时类型安全验证将无法实现。如果允许该语法,编译器将无法验证代码的安全性,因此您将不得不对所有内容进行运行时检查。因此,无论如何,这都是不可能的,因为这不是一个好主意。 - Lasse V. Karlsen
我应该如何设计动物园和动物笼子类?我希望狮子笼只能容纳狮子。而且我希望动物笼可以随着更多的笼子而扩展。 - user2714000

2

由于你想拥有多个笼子,但每种类型的笼子只能容纳一种动物,所以你的模型略有偏差。

我重写了代码如下:

  • IZooAnimal没有改变。
  • 存在一个协变接口ICage,可以接受任何类型的IZooAnimal。这允许您为每种类型的动物拥有强类型的笼子。
  • 然后,我有一个实现ICage的具体Cage实现。 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);

    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接