【问题标题】:Generics and Class Inheritance confusion泛型和类继承混淆
【发布时间】:2015-05-13 13:53:29
【问题描述】:

我有以下代码块作为我遇到的问题的简化示例。但我收到一个错误,声称我无法将一种类型转换为另一种类型。我用 LINQPad 测试了一下。

void Main()
{
    LivingThing<Appendage> mysteryAnimal = new Cat();
}

public class Appendage { }
public class Paw : Appendage { }

public class LivingThing<TExtremity> where TExtremity : Appendage { }
public class Animal<TExtremity> : LivingThing<TExtremity> where TExtremity : Appendage { }
public class Cat : Animal<Paw> { }

当我知道 Cat 的定义使用 LivingThingAppendage 的子类时,为什么我不能将 Cat 转换为 LivingThing&lt;Appendage&gt;

【问题讨论】:

  • 因为LivingThing&lt;Paw&gt; 不是LivingThing&lt;Appendage&gt; - 阅读协方差...
  • 类是不变的——你可以创建一个ILivingThing&lt;out TExtremity&gt;接口,然后你可以做ILivingThing&lt;Appendage&gt; mysteryAnimal = new Cat();
  • 哦,所以类是协变的,但不是泛型? LivingThing&lt;Appendage&gt; mysteryAnimal = new Animal&lt;Appendage&gt;(); //worksLivingThing&lt;Appendage&gt; mysteryAnimal = new LivingThing&lt;Paw&gt;(); //does not
  • 所谓的重复显然是相关的,但它没有回答 OP 的问题,即为什么不改变周围就不可能做他想做的事。投票重新开放。

标签: c# generics inheritance covariance


【解决方案1】:

通过向其中一个类添加方法,可以更容易地了解为什么如果不进行一些修改就无法工作:

public class LivingThing<TExtremity> where TExtremity : Appendage {
    private TExtremity extremity;
    public void SetExtremity(TExtremity e) {
        extremity = e;
    }
}

现在让我们假设 C# 允许您完成任务。然后它应该让你这样做:

public class Hand : Appendage { }
...
// Let's pretend this works
LivingThing<Appendage> cat = new Cat();
// Now that C# let us do the assignment above, it must allow this too,
// because Cat is a LivingThing and Hand is an Appendage:
cat.SetExtremity(new Hand());

糟糕,我们有一只手猫! C# 不应该让我们这样做。

如果LivingThing返回 TExtremity 的方法,那么你可能会做你想做的事。 C# 提供了定义继承层次结构的方法,使您可以根据您所尝试的方式灵活地进行分配。这是您修改后的代码:

void Main()
{
    ILivingThing<Appendage> mysteryAnimal = new Cat();
}

public class Appendage { }
public class Paw : Appendage { }

public interface ILivingThing<out TExtremity> where TExtremity : Appendage { }
// You have a choice of keeping Animal a class. If you do, the assignment
// Animal<Appendage> mysteryAnimal = new Cat()
// would be prohibited.
public interface IAnimal<TExtremity> : ILivingThing<out TExtremity> where TExtremity : Appendage { }
public class Cat : Animal<Paw> { }

有一个问题:ILivingThing&lt;TExtremity&gt;IAnimal&lt;TExtremity&gt; 都不允许有 TExtremity 类型的可设置属性或将 TExtremity 作为参数的方法。

【讨论】:

  • 非常有趣...我将您的示例弹出到 LINQPad 中进行尝试,但它在一些问题上遇到了问题。 IAnimal 界面上的ILivingThing&lt;out TExtremity&gt; 显然不需要是out?而Cat需要继承接口(不是我之前的类)。
  • 我熟悉out 方法的参数,但不使用泛型。是否有一个很好的问题或涵盖它们的文章?
  • @NickAlbrecht Eric Lippert,C# 编译器的设计者之一,有一系列关于协方差的非常好的帖子。在this post 中,他讨论了在模板参数中使用inout 的语法选项,然后再将其添加到语言中(参见选项#5)。
  • 太棒了,非常感谢您指出正确的方向。感谢您抽出宝贵时间帮助我:-)
【解决方案2】:

您正在尝试做的事情称为“协方差”;将具有更多派生的泛型参数的泛型类的实例分配给具有较少派生参数的类的变量。

在 C# 中,类不支持此功能。对于接口,必须明确指定。以下将编译:

void Main()
    {
        ILivingThing<Appendage> mysteryAnimal = new Cat();
    }

    public class Appendage { }
    public class Paw : Appendage { }

    public interface ILivingThing<out TExtremity> where TExtremity : Appendage { }
    public class Animal<TExtremity> : ILivingThing<TExtremity> where TExtremity : Appendage { }
    public class Cat : Animal<Paw> { }

这对于您的实际代码是否可以接受取决于 LivingThing 的定义方式;如果它是“标记类”(没有成员;仅作为派生点而存在,将其子类标识为 LivingThings),或者是没有非抽象成员的抽象类,它应该像魅力一样工作。如果您在此类中有成员代码,则需要从该类中提取协变接口。

【讨论】:

  • 如果我两者都做,在基类中有成员代码(非抽象),但使用接口作为对我的泛型 where 约束的限制?
  • 好的。太棒了,我将不得不研究我是如何传递这些实例的,并确定我什么时候真正需要一个强类型类或只需要一个接口,看看我是否能把它全部整理好。非常感谢:-)
猜你喜欢
  • 2012-09-13
  • 2011-04-25
  • 2016-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多