【问题标题】:Can i call a method that takes IEnumerable<Baseclass<superclass>> with IEnumerable<Derivedclass<subclass>>我可以用 IEnumerable<Derivedclass<subclass>> 调用一个采用 IEnumerable<Baseclass<superclass>> 的方法吗
【发布时间】:2018-06-21 08:13:26
【问题描述】:

我有一些与此等效的代码(尽管这是一种简化):

namespace AnimalHospital
{
    public class Animal { }
    public class Dog : Animal { }
    public class Cat : Animal { }

    public interface Vet<T> where T : Animal 
    { 
        void takeCareOf(T animal); 
    }
    public abstract class SpecializedDogVet<T> : Vet<T> where T : Dog
    {
       public abstract void takeCareOf(T dog); 
    }

    public abstract class SpecializedCatVet<T> : Vet<T> where T : Cat
    {
        public abstract void takeCareOf(T cat);
    }

    public class AnimalHospital
    {
        public IList<SpecializedCatVet<Cat>> CatVets = new List<SpecializedCatVet<Cat>>();
        public IList<SpecializedDogVet<Dog>> DogVets = new List<SpecializedDogVet<Dog>>();

        private void treatSickAnimal(IEnumerable<Vet<Animal>> vets, Animal patient)
        {
            foreach(var vet in vets)
            {
                vet.takeCareOf(patient);
            }
        }

        public void treatSickCat(Cat cat)
        {
            treatSickAnimal(CatVets, cat);
        }

        public void treatSickDog(Dog dog)
        {
            treatSickAnimal(DogVets, dog);
        }
    }
}

我收到一个错误,告诉我转换来自:

IList&lt;SpecializedCatVet&lt;Cat&gt;&gt;IEnumerable&lt;Vet&lt;Animal&gt;&gt; 是不可能的。怎么会这样?在此之前它们是非泛型的,而且我还有一些其他问题,因为我无法覆盖 vet 接口 takeCareOf 方法。我曾预料到,由于 Animal 的 IEnumerable 可以很容易地用 Dog 列表实例化,因此解析泛型集合也是如此,只要它们的类型参数是所需类型的派生词。但事实并非如此,我似乎无法弄清楚为什么或如何正确地做到这一点。

感谢阅读。

更新:我接受 JLRishe 的回答,非常有意义。非常感谢。

【问题讨论】:

  • 查看IEnumerable 是否与您的问题相关联的一种简单方法是在调用treatSickAnimal (treatSickAnimal(DogVets.AsEnumerable*(), dog)) 之前使用System.Linq 中的.AsEnumerable()。没有链接?将其放入您的示例中:)
  • 不,我从一开始就知道集合类型不是问题,而是类型参数,我只是不明白为什么:)

标签: c# .net oop .net-core


【解决方案1】:

这是不允许的,因为如果允许,你可以这样:

var cat = new Cat();

treatSickAnimal(DogVets, cat);

基本上是试图强迫狗兽医治疗猫。

你可以通过使你的方法在动物参数上通用来解决这个问题:

private void treatSickAnimal<T>(IEnumerable<Vet<T>> vets, T patient) where T : Animal
{
    foreach (var vet in vets)
    {
        vet.takeCareOf(patient);
    }
}

这应该可以让您的代码正确编译,并确保您不会试图强迫任何狗兽医治疗猫。


旁注 - 除非您计划拥有 DogCat 的专门类型(子类)以及专门针对这些子类的兽医,否则您可以将 SpecializedDogVetSpecializedCatVet 的定义简化为这个:

public abstract class SpecializedDogVet : Vet<Dog>
{
    public abstract void takeCareOf(Dog dog);
}

public abstract class SpecializedCatVet : Vet<Cat>
{
    public abstract void takeCareOf(Cat cat);
}

然后你会像这样引用类型:

public IList<SpecializedCatVet> CatVets = new List<SpecializedCatVet>();
public IList<SpecializedDogVet> DogVets = new List<SpecializedDogVet>();

【讨论】:

  • 感谢旁注。非常有帮助。我不知道:)
【解决方案2】:

你不能因为你的Vet&lt;T&gt; 接口当前是不变的,请注意你可以通过指定它是输入参数来使其逆变:

public interface Vet<in T> where T : Animal
{
    void takeCareOf(T animal);
}

这是可能的,因为 T 到处都被用作 input 参数。

为了编译你的代码,你可以按照@JLRishe 所说的去做,或者你可以改变你的逻辑并制作T output 参数,但是为了做到这一点,你需要改变你的Vet 实现,所以到处使用 T 它需要在输出位置只是一个例子:

public interface Vet<out T> where T : Animal
{
    T takeCareOf();
}

因此,总而言之,您可以将基类传递给接口或委托,它们的类型参数定义为 output,当类型参数定义为 in放。

这实际上是一篇关于协变和逆变的令人惊叹的文章,如果我让你着迷,你一定要检查它http://tomasp.net/blog/variance-explained.aspx/

【讨论】:

  • 使T 成为out 参数在这种情况下没有任何意义,因为T takeCareOf() 没有任何意义。兽医应该凭空变出动物吗?
  • @JLRishe 我的意思不是说什么有意义,而是向他展示为什么它一开始没有编译。 给一个人一条鱼,你可以喂他一天。教一个人钓鱼,你养他一辈子
  • 很公平。绝对值得详细说明 inout 是如何工作的,以及它们如何影响泛型类的使用方式,但如果你打算这样做,我认为解释为什么它们不会是有意义的不适用于OP的情况。
猜你喜欢
  • 1970-01-01
  • 2020-01-18
  • 1970-01-01
  • 1970-01-01
  • 2011-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多