2008-09-05 324 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。

相關問題