2008-09-05 81 views
8

我有一些继承问题,因为我有一组相互关联的抽象类,需要全部重写以创建客户端实现。理想情况下,我想这样做如下:为什么不继承我认为它应该工作的方式工作?

abstract class Animal 
{ 
    public Leg GetLeg() {...} 
} 

abstract class Leg { } 

class Dog : Animal 
{ 
    public override DogLeg Leg() {...} 
} 

class DogLeg : Leg { } 

这将使用Dog类任何人都可以自动获得狗腿和使用动物类来获得腿的人允许。问题是重写的函数必须与基类具有相同的类型,所以不能编译。我不明白为什么它不应该,因为DogLeg可以隐式地转化为Leg。我知道有很多方法可以解决这个问题,但我更加好奇为什么在C#中这是不可能的/实现的。

编辑:我修改了这个有点,因为我实际上在我的代码中使用属性而不是函数。

编辑:我改回了功能,因为答案只适用于这种情况(协方差属性上的设定功能不应该工作参数的值)。对不起,波动!我意识到这使得很多答案似乎无关紧要。

回答

15

简而言之,GetLeg在其返回类型中是不变的。长答案可以在这里找到:Covariance and contravariance

我想补充一点,虽然继承通常是大多数开发人员从工具箱中提取的第一个抽象工具,但几乎总是可以使用组合来代替。对于API开发人员来说,组合的工作稍微多一些,但是使API对消费者更有用。

+0

你的差异声明没有意义。 在子类型:协方差=相关类型被子类型& Contravariance =相关类型获得超类型。 当C#只允许一个不变的(相同的)返回类型时,这个问题表示一个协变(在子类型中使用的子类型)返回类型。 – 2008-09-07 16:58:20

+0

您链接到的那些文章是关于泛型类型差异。问题是关于返回类型协方差。我在那些文章中明确指出我不*在谈论返回类型协方差。 – 2010-07-17 06:36:10

2

GetLeg()必须返回腿作为覆盖。但是,您的Dog类仍然可以返回DogLeg对象,因为它们是Leg的子类。然后客户可以将它们作为狗腿进行投射和操作。

public class ClientObj{ 
    public void doStuff(){ 
    Animal a=getAnimal(); 
    if(a is Dog){ 
     DogLeg dl = (DogLeg)a.GetLeg(); 
    } 
    } 
} 
6

狗应该返回一个Leg而不是DogLeg作为返回类型。实际的课程可能是DogLeg,但重点是分离,因此Dog的用户不必了解DogLegs,他们只需要了解腿。

变化:

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,你必须返回一个Leg。这就是抽象类中有一个方法的要点。将该方法传播给它的childern。如果您希望Dog的用户了解DogLegs,请创建一个名为GetDogLeg的方法并返回DogLeg。

如果你可以做问题提问者,那么动物的每个用户都需要知道所有的动物。

+0

无关紧要。问题是为什么不能用一个方法重写一个方法,该方法的返回值是被重写者返回的子类型。 – 2008-09-07 17:16:28

+1

我可以理解动物的用户应该只知道腿部对象的说法,但我不太相信这应该从Dog类的用户隐藏。 – Luke 2008-09-08 17:01:25

0

对,我明白我可以投,但这意味着客户必须知道狗有DogLegs。我想知道的是,如果存在技术上的原因,为什么这是不可能的,因为存在隐式转换。

0

@Brian Leahy 很明显,如果你只是作为一条腿进行操作,那么没有必要或理由去施放。但是如果有DogLeg或Dog特定的行为,有时候演员阵容是必要的。

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()的返回类型是腿。

0

您也可以返回Leg和/或DogLeg实现的接口ILeg。

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上使用特殊驼峰方法的功能。

1

也许是更容易看到问题的一个例子:

Animal dog = new Dog(); 
dog.SetLeg(new CatLeg()); 

现在应该编译如果你是狗编制,但我们可能不希望这样的突变体。

相关的问题是狗[]是动物[],或IList <狗> IList <动物>?

0

要记住的重要事情是,你可以使用派生类型每次使用的基本类型的地方(你可以通过狗的任何方法/属性/字段/变量,预计动物)

让我们这个功能:

public void AddLeg(Animal a) 
{ 
    a.Leg = new Leg(); 
} 

一个完全有效的功能,现在让我们把这样的功能:

AddLeg(new Dog()); 

如果属性Dog.Leg是腿型的AddLeg函数s的不uddenly包含错误,无法编译。

2

并不是说它有很多用处,但是可能有趣的是,Java确实支持协变性返回,所以这将会如您所愿地工作。除了很明显,Java没有属性;)

12

很明显,如果你在破损的DogLeg上操作 ,你需要一个强制转换。

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

这是一个完全有效的愿望,有签名为覆盖方法有返回类型,它是在返回类型的子类型重写方法(phew)。毕竟,它们是运行时兼容的。

但是C#在重写的方法中还不支持“协变返回类型”(不像C++ [1998] & Java [2004])。

你需要解决,并为可预见的未来做什么,如埃里克利珀在his blog [2008年6月19日]说:

那种变异被称为“返回类型协方差” 。

我们没有计划在C#中实现这种差异。

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 { } 
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。

相关问题