【问题标题】:C# generics with interfaces带有接口的 C# 泛型
【发布时间】:2013-08-25 02:46:40
【问题描述】:

我正在尝试学习如何使用 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&lt;ConsoleApplication2.Lion&gt;' 转换为 'ConsoleApplication2.ZooCage&lt;ConsoleApplication2.IZooAnimal&gt;'

为了使我的程序运行,我必须做哪些更改?

【问题讨论】:

  • 了解协变和逆变...也许可以开始here?

标签: c# generics


【解决方案1】:

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

【讨论】:

  • 确实 - 比我的更优雅、更清洁的解决方案! :)
【解决方案2】:

您不应该使用实现接口的具体类型来定义列表,而是使用接口:

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

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

【讨论】:

  • 这将防止编译错误,但它破坏了使用泛型的整个目的。如果你打算这样做,最好不要让 ZooCage 通用。它也不满足“动物园笼子可以容纳相同类型的动物”的要求,因为所有笼子都可以容纳任何类型的动物。
  • 问题是 OP 想要的东西是不可能的,不仅仅是因为编译器这么说,还因为编译时类型安全验证会消失。如果您允许该语法,编译器将无法验证代码是否安全,因此您将返回运行时检查所有内容。所以不管你怎么切,这是不可能的,因为这不是一个好主意。
  • 我应该如何设计 Zoo 和 Zoocage 类?我希望狮子笼只能容纳狮子和狮子。我希望 Zoocage 能够与更多的笼子一起成长。
【解决方案3】:

由于您想要拥有多个笼子,但每种笼子只能容纳一只动物,因此您的模型略有偏差。

我将代码改写如下:

  • IZooAnimal 不变。
  • 有一个协变接口ICage 可以接受任何类型的IZooAnimal。这使您可以为每种类型的动物拥有一个强类型的笼子。
  • 然后,我有一个Cage 的具体实现ICageCage 是通用的,但您可以轻松地将其设为抽象类,然后制作特定于动物的笼子实现。例如,如果您的斑马需要喂草,而您的狮子需要喂肉,您可以专门实现它们的笼子。

这是完整的代码:

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

    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-14
    • 1970-01-01
    • 2021-10-03
    • 2020-03-24
    • 2023-03-31
    • 1970-01-01
    • 2022-12-18
    • 1970-01-01
    相关资源
    最近更新 更多