8

クライアント実装を作成するためにすべてを一緒にオーバーライドする必要がある相互に関連する抽象クラスのグループがあるため、いくつかの継承の問題が発生しています。理想的には、次のようなことをしたいと思います。

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#でこれが不可能/実装されていない理由についてはもっと興味があります。

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

編集:答えはその状況にのみ適用されるため、関数に戻しました(プロパティの集合関数の値パラメーターの共分散は機能しないはずです)。変動してすみません!私はそれが多くの答えを無関係に見えるようにすることを理解しています。

4

17 に答える 17

15

簡単な答えは、GetLegはその戻り型で不変であるということです。長い答えはここにあります:共変性と反変性

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

于 2008-09-05T21:37:40.180 に答える
12

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

于 2008-09-05T22:38:45.340 に答える
6

Dogは、戻りタイプとしてDogLegではなくLegを返す必要があります。実際のクラスはDogLegである可能性がありますが、重要なのは、DogのユーザーがDogLegについて知る必要がなく、Legについてのみ知る必要があるように分離することです。

変化する:

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をオーバーライドする場合は、レッグを返す必要があります。それが抽象クラスにメソッドを持つことのポイントです。そのメソッドを子孫に伝播すること。DogのユーザーにDogLegについて知ってもらいたい場合は、GetDogLegというメソッドを作成し、DogLegを返します。

質問者が望むようにできる場合は、Animalのすべてのユーザーがすべての動物について知る必要があります。

于 2008-09-05T21:35:18.330 に答える
4

オーバーライドされたメソッドのシグネチャに、オーバーライドされたメソッド ( phew ) の戻り値の型のサブタイプである戻り値の型を持たせることは、完全に有効な要求です。結局のところ、それらは実行時の型と互換性があります。

しかし、C# はオーバーライドされたメソッドで「共変の戻り値の型」をまだサポートしていません (C++ [1998] や Java [2004] とは異なります)。

Eric Lippert が彼のブログ [2008 年 6 月 19 日]で述べているように、次のように回避し、近い将来に対処する必要があります。

この種の分散は、「戻り型の共分散」と呼ばれます。

C# でそのような差異を実装する予定はありません。

于 2008-09-06T06:57:34.173 に答える
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メソッドを持つ機能を保持しながら。

于 2008-09-05T21:48:30.233 に答える
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();}
 } 
于 2008-09-06T04:37:04.370 に答える
2

問題を引き起こしている概念は、http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)で説明されています。

于 2008-09-05T21:38:51.677 に答える
2

あまり役に立たないというわけではありませんが、Javaが共変リターンをサポートしていることに注意するのは興味深いかもしれません。したがって、これは期待どおりに機能します。明らかにJavaにプロパティがないことを除いて;)

于 2008-09-05T22:03:05.580 に答える
2

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

public class ClientObj{
    public void doStuff(){
    Animal a=getAnimal();
    if(a is Dog){
       DogLeg dl = (DogLeg)a.GetLeg();
    }
  }
}
于 2008-09-05T21:33:00.707 に答える
1

おそらく、例を使用して問題を確認する方が簡単です。

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

Dogでコンパイルされている場合はコンパイルする必要がありますが、おそらくそのようなミュータントは必要ありません。

関連する問題は、Dog[]をAnimal[]にするか、IList<Dog>をIList<Animal>にするかです。

于 2008-09-05T21:54:04.977 に答える
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 が返されます。

于 2008-09-16T15:39:53.133 に答える
0

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

于 2008-09-05T21:36:05.333 に答える
0

@Brian Leahy明らかに、レッグとしてのみ操作している場合は、キャストする必要や理由はありません。ただし、DogLegまたはDog固有の動作がある場合は、キャストが必要な理由がある場合があります。

于 2008-09-05T21:38:30.600 に答える
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();になります。DogLeg.Kick()(メソッドLeg.kick()が存在すると仮定します)の場合、Dog.GetLeg()の戻り型が足。

于 2008-09-05T21:46:54.027 に答える
0

Legおよび/またはDogLegの両方が実装するインターフェースILegを返すこともできます。

于 2008-09-05T21:47:21.187 に答える
0

覚えておくべき重要なことは、基本型を使用するすべての場所で派生型を使用できることです(DogをAnimalを期待する任意のメソッド/プロパティ/フィールド/変数に渡すことができます)

この関数を見てみましょう:

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

完全に有効な関数です。次のような関数を呼び出しましょう。

AddLeg(new Dog());

プロパティDog.LegがLegタイプでない場合、AddLeg関数に突然エラーが含まれ、コンパイルできなくなります。

于 2008-09-05T21:54:20.703 に答える
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 { }
于 2008-09-16T00:46:20.377 に答える