2008-09-05 9 views
8

私はいくつかの継承の問題を抱えています。これは、クライアント実装を作成するために相互に関連する抽象クラスのグループをすべてオーバーライドする必要があるためです。理想的には私は、次のような何かをしたいと思います:これは誰でも自動的にドッグレッグして足を取得するにはAnimalクラスを使用して、誰を取得するには、Dogクラスを使用できるようになる継承がうまくいかないと思うのはなぜですか?

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

abstract class Leg { } 

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

class DogLeg : Leg { } 

。問題は、オーバーライドされた関数は基本クラスと同じ型でなければならないため、これはコンパイルされません。私はDogLegが暗黙のうちにLegにキャスト可能であるので、なぜそうはならないのか分かりません。私はこれの周りに多くの方法があることを知っていますが、なぜこれが可能ではない/ C#で実装されているより興味がある。

EDIT:私はコードの中で実際に関数の代わりにプロパティを使用しているので、これを多少変更しました。

EDIT:答えは唯一のそのような状況(プロパティの設定機能の値パラメータの共分散すべきではない仕事)に適用されますので、私は、関数に戻ってそれを変更しました。変動のために申し訳ありません!私はそれが多くの答えを無関係と思わせることに気づきます。

答えて

15

簡単な答えは、GetLegがその戻り型で不変であることです。長い答えはここにあります:Covariance and contravariance

継承は、通常、ほとんどの開発者がツールボックスから引き出した最初の抽象ツールですが、ほとんどの場合、代わりに合成を使用することができます。構成はAPI開発者の方がやや面倒ですが、APIをそのコンシューマーにとってより便利にします。

+0

あなたの声明は理にかなっていません。 サブタイプの場合:共分散=関連タイプがサブタイプになります。 コントラスト変動=関連タイプがスーパータイプになります。 この質問は、C#が不変(同じ)戻り型のみを許可するときに、共変量(サブタイプで使用されるサブタイプ)の戻り値の型を表します。 –

+0

あなたがリンクしたそれらの記事は、一般的なタイプの差異に関するものです。問題は、戻り値の共分散についてです。これらの記事では、私が戻り値の共分散について話していないことを明示しています。 –

+0

Eric Lippert:しかし、それらは本質的につながっています。ここをクリックしてください:http://apocalisp.wordpress.com/2009/08/27/hostility-toward-subtyping/ – Apocalisp

2

GetLeg()は、オーバーライドするためにLegを返す必要があります。しかし、あなたのDogクラスはLegの子クラスなので、DogLegオブジェクトを返すことができます。クライアントはドングルとしてキャストして操作することができます。

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

犬は戻り値の型としてDogLegを返しません。実際のクラスはDogLegでも構いませんが、DogのユーザーはDogLegについて知る必要はなく、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関数が抽象的な脚を返すためです。 GetLegを上書きする場合は、脚を返す必要があります。抽象クラスでメソッドを持つ点です。そのメソッドをchildernに伝播する。 DogのユーザにDogLegについて知りたければ、GetDogLegというメソッドを作り、DogLegを返します。

質問者が質問したいと思うのであれば、Animalのすべてのユーザーはすべての動物について知る必要があります。

+0

無関係。問題は、サブタイプが、その戻り値がオーバーライドの戻り値のサブタイプであるメソッドを持つメソッドをオーバーライドできないかどうかを質問します。 –

+1

私は、AnimalのユーザがLegオブジェクトについてのみ知っているべきであるという議論を理解することができますが、私はDogクラスのユーザから隠されるべきではないと確信しています。 – Luke

0

私はちょうどキャストできますが、それはクライアントがDogLegsを持っていることをクライアントが知っていなければならないことを意味します。私が疑問に思うのは、暗黙的な変換が存在する場合、これが不可能な技術的理由がある場合です。

0

@Brian Leahy 明らかに、脚としてしか操作していない場合は、キャストの必要性または理由はありません。しかし、いくつかのDogLegまたはDog固有の動作がある場合、キャストが必要な理由があることがあります。あなたが問題を引き起こしている

0

で記述されています。 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()です。 Dog.GetLeg()の戻り値の型が返されたとしても、DogLeg.Kick()(私はLeg.kick()メソッドが存在すると仮定しています)では、DogLegであると宣言された戻り値の型は不必要です。脚。

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に特別なHumpメソッドを持たせる能力は依然として残っています。あなたは犬をコンパイルしているが、我々は、おそらくこのような変異体をしたくない場合は今ではコンパイルする必要があり

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

1

おそらくそれは例の問題点を確認する方が簡単です。

関連する問題は、Dog []がAnimal []またはIListである必要があります。< Dog> IList < Animal>?

0

覚えておくべき重要なことは、あなたがのは、この機能を見てみましょうあなたは基本型を使用するすべての場所が

(あなたは動物を期待する任意の方法/プロパティ/フィールド/変数に犬を渡すことができます)派生型を使用することができるということです:プロパティDog.Legは脚AddLeg関数sタイプでない場合

AddLeg(new Dog()); 

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

A完全に有効な機能は、今のはそのような関数を呼び出してみましょうuddenlyにはエラーがあり、コンパイルできません。

2

多用ですが、Javaが共変リターンをサポートしていることに注意するのは興味深いことでしょう。明らかに、Javaにはプロパティがありません;

12

壊れたDogLegで を使用している場合は、明らかにキャストが必要です。

+1

haha​​ha素敵な言葉遊び:) –

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

この優雅な見た目のソリューションは、C#一般的な分散サポート。 –

+0

どのような複雑さですか?この解決法はいかなる変更も必要としない。 –

+0

もっと複雑な犬が必要な場合はどうすればいいですか? DogTail、DogTeeth、DogEtcを持つもの.. – Seiti

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を返します。

関連する問題