我有一些继承问题,因为我有一组相互关联的抽象类需要全部重写以创建客户端实现。理想情况下,我想做类似以下的事情:
abstract class Animal
{
public Leg GetLeg() {...}
}
abstract class Leg { }
class Dog : Animal
{
public override DogLeg Leg() {...}
}
class DogLeg : Leg { }
这将允许使用Dog类的任何人自动获取DogLegs以及任何使用Animal类获取Legs的人。问题是被覆盖的函数必须与基类具有相同的类型,因此不会编译。我不明白为什么它不应该,因为DogLeg可以隐式地施放到Leg。我知道有很多方法可以解决这个问题,但我更好奇为什么在C#中无法实现这一点。
编辑:我对此进行了一些修改,因为我实际上是在代码中使用属性而不是函数。
编辑:我将其更改回功能,因为答案仅适用于该情况(属性的设置函数的值参数的协方差不应工作) 。对不起波动!我意识到它使许多答案看起来无关紧要。
答案 0 :(得分:15)
简短的回答是GetLeg的返回类型是不变的。答案很长,可以在这里找到:Covariance and contravariance
我想补充一点,虽然继承通常是大多数开发人员从他们的工具箱中取出的第一个抽象工具,但几乎总是可以使用组合。对于API开发人员来说,组合工作稍微多一些,但是使API对其消费者更有用。
答案 1 :(得分:12)
答案 2 :(得分:6)
狗应该返回Leg而不是DogLeg作为返回类型。实际的课程可能是DogLeg,但重点是解耦,因此Dog的用户不必了解DogLegs,他们只需要了解Legs。
变化:
class Dog : Animal
{
public override DogLeg GetLeg() {...}
}
为:
class Dog : Animal
{
public override Leg GetLeg() {...}
}
不要这样做:
if(a instanceof Dog){
DogLeg dl = (DogLeg)a.GetLeg();
它违背了编程为抽象类型的目的。
隐藏DogLeg的原因是抽象类中的GetLeg函数返回Abstract Leg。如果你要覆盖GetLeg,你必须返回一条腿。这就是在抽象类中使用方法的重点。将该方法传播给它的孩子们。如果你想让Dog的用户知道DogLegs,请创建一个名为GetDogLeg的方法并返回DogLeg。
如果您可以像提问者那样做,那么Animal的每个用户都需要了解所有动物。
答案 3 :(得分:4)
完全有效的愿望是让签名重写方法的返回类型是重写方法( phew )中返回类型的子类型。毕竟,它们与运行时类型兼容。
但是C#在重写方法中还不支持“协变返回类型”(与C ++ [1998]& Java [2004]不同)。
正如Eric Lippert在his blog所述,你需要在可预见的未来努力工作并做好准备。 [2008年6月19日]:
这种方差称为“返回类型协方差”。
我们没有计划在C#中实现这种差异。
答案 4 :(得分:3)
abstract class Animal
{
public virtual Leg GetLeg ()
}
abstract class Leg { }
class Dog : Animal
{
public override Leg GetLeg () { return new DogLeg(); }
}
class DogLeg : Leg { void Hump(); }
这样做,然后你可以在你的客户端利用抽象:
Leg myleg = myDog.GetLeg();
然后,如果你需要,你可以施展它:
if (myleg is DogLeg) { ((DogLeg)myLeg).Hump()); }
完全做作,但关键是你可以做到这一点:
foreach (Animal a in animals)
{
a.GetLeg().SomeMethodThatIsOnAllLegs();
}
虽然仍保留在Doglegs上使用特殊驼峰方法的能力。
答案 5 :(得分:3)
您可以使用泛型和接口在C#中实现它:
abstract class Leg { }
interface IAnimal { Leg GetLeg(); }
abstract class Animal<TLeg> : IAnimal where TLeg : Leg
{ public abstract TLeg GetLeg();
Leg IAnimal.GetLeg() { return this.GetLeg(); }
}
class Dog : Animal<Dog.DogLeg>
{ public class DogLeg : Leg { }
public override DogLeg GetLeg() { return new DogLeg();}
}
答案 6 :(得分:2)
GetLeg()必须返回Leg作为覆盖。但是,您的Dog类仍然可以返回DogLeg对象,因为它们是Leg的子类。然后,客户可以像doglegs一样投射和操作它们。
public class ClientObj{
public void doStuff(){
Animal a=getAnimal();
if(a is Dog){
DogLeg dl = (DogLeg)a.GetLeg();
}
}
}
答案 7 :(得分:2)
并不是说它有多大用处,但注意Java确实支持协变返回可能很有意思,因此这将完全符合您的预期。显然Java没有属性;)
答案 8 :(得分:2)
答案 9 :(得分:1)
也许通过示例更容易看到问题:
Animal dog = new Dog();
dog.SetLeg(new CatLeg());
现在应该编译,如果你编译了Dog,但我们可能不想要这样的突变体。
相关问题是Dog []应该是Animal [],还是IList&lt; Dog&gt; IList&lt; Animal&gt;?
答案 10 :(得分:1)
C#有明确的接口实现来解决这个问题:
abstract class Leg { }
class DogLeg : Leg { }
interface IAnimal
{
Leg GetLeg();
}
class Dog : IAnimal
{
public override DogLeg GetLeg() { /* */ }
Leg IAnimal.GetLeg() { return GetLeg(); }
}
如果您通过类型为Dog的引用拥有Dog,则调用GetLeg()将返回DogLeg。如果你有相同的对象,但引用的类型是IAnimal,那么它将返回一个Leg。
答案 11 :(得分:0)
@Luke
我认为你或许误解了继承。 Dog.GetLeg()将返回DogLeg对象。
public class Dog{
public Leg GetLeg(){
DogLeg dl = new DogLeg(super.GetLeg());
//set dogleg specific properties
}
}
Animal a = getDog();
Leg l = a.GetLeg();
l.kick();
调用的实际方法是Dog.GetLeg();和DogLeg.Kick()(我假设一个方法Leg.kick()存在)那里,声明的返回类型是DogLeg是不必要的,因为这是返回的,即使Dog.GetLeg()的返回类型是腿。
答案 12 :(得分:0)
您还可以返回Leg和/或DogLeg都实现的接口ILeg。
答案 13 :(得分:0)
要记住的重要一点是,您可以在每个使用基类型的地方使用派生类型(您可以将Dog传递给任何期望Animal的方法/属性/字段/变量)
我们来看看这个功能:
public void AddLeg(Animal a)
{
a.Leg = new Leg();
}
一个完全有效的函数,现在让我们调用函数:
AddLeg(new Dog());
如果属性Dog.Leg不是Leg类型,则AddLeg函数突然包含错误,无法编译。
答案 14 :(得分:0)
@Brian Leahy 显然,如果你只是作为一个腿进行操作,就没有必要或没有理由去施展。但是如果有一些DogLeg或Dog特定的行为,有时候有理由认为演员是必要的。
答案 15 :(得分:0)
是的,我知道我可以施展,但这意味着客户必须知道狗有狗腿。我想知道的是,如果存在技术原因,为什么这是不可能的,因为存在隐式转换。
答案 16 :(得分:0)
您可以通过使用具有适当约束的泛型来实现您想要的效果,如下所示:
abstract class Animal<LegType> where LegType : Leg
{
public abstract LegType GetLeg();
}
abstract class Leg { }
class Dog : Animal<DogLeg>
{
public override DogLeg GetLeg()
{
return new DogLeg();
}
}
class DogLeg : Leg { }