C#での複数の継承


212

多重継承は悪いので(ソースをより複雑にします)、C#はそのようなパターンを直接提供しません。しかし、この能力があると役立つ場合があります。

たとえば、インターフェイスとそのような3つのクラスを使用して、不足している多重継承パターンを実装できます。

public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class First:IFirst 
{ 
    public void FirstMethod() { Console.WriteLine("First"); } 
}

public class Second:ISecond 
{ 
    public void SecondMethod() { Console.WriteLine("Second"); } 
}

public class FirstAndSecond: IFirst, ISecond
{
    First first = new First();
    Second second = new Second();
    public void FirstMethod() { first.FirstMethod(); }
    public void SecondMethod() { second.SecondMethod(); }
}

インターフェイスの1つにメソッドを追加するたびに、FirstAndSecondクラスも変更する必要があります。

C ++で可能なように、複数の既存のクラスを1つの新しいクラスに注入する方法はありますか?

たぶん、ある種のコード生成を使用した解決策はありますか?

または、次のようになります(架空のc#構文)。

public class FirstAndSecond: IFirst from First, ISecond from Second
{ }

そのため、インターフェイスの1つを変更するときにFirstAndSecondクラスを更新する必要はありません。


編集

多分それは実用的な例を考える方が良いでしょう:

プロジェクト内の別の場所で既に使用している既存のクラス(ITextTcpClientに基づくテキストベースのTCPクライアントなど)があります。これで、Windowsフォーム開発者が簡単にアクセスできるようにクラスのコンポーネントを作成する必要性を感じました。

私が知る限り、現在これを行うには2つの方法があります。

  1. コンポーネントから継承され、FirstAndSecondで示されているように、クラス自体のインスタンスを使用してTextTcpClientクラスのインターフェースを実装する新しいクラスを記述します。

  2. TextTcpClientから継承し、どういうわけかIComponentを実装する新しいクラスを記述します(実際にはまだこれを試していません)。

どちらの場合も、クラスごとではなくメソッドごとに作業を行う必要があります。TextTcpClientとComponentのすべてのメソッドが必要であることを知っているので、これら2つを1つのクラスに結合するのが最も簡単なソリューションです。

衝突を回避するために、後で結果が変更される可能性があるコード生成によってこれを行うことができますが、これを手で入力することは、お尻の純粋な痛みです。


これが単なる偽装の多重継承ではない場合、どのように複雑さを軽減できますか?
harpo

3.5の新しい拡張メソッドとその機能(静的メンバー呼び出しの生成)について考えると、これは.NET言語の次の進化の1つかもしれません。
ラリー

なぜ私はなぜそうしないのでしょうか...クラスA:クラスB:クラスC?
Chibueze Opata 2012

@NazarMerza:リンクが変更されました。今:多重継承の問題
Craig McQueen

9
プロパガンダに騙されないでください。あなたのまさに例は、多重継承が有用であり、インターフェースがそれの欠如に対する単なる回避策であることを示しています
Kemal Erdogan

回答:


125

多重継承は悪いので(ソースをより複雑にします)、C#はそのようなパターンを直接提供しません。しかし、この能力があると役立つ場合があります。

C#および.net CLRは、C#、VB.net、および他の言語間で相互運用する方法をまだ結論付けていないため、MIを実装していません。「ソースをより複雑にする」ためではありません。

MIは有用な概念であり、未回答の質問は次のようなものです。「異なるスーパークラスに複数の共通基本クラスがある場合はどうしますか?

Perlは、MIが機能し、適切に機能する、私がこれまでに使用した唯一の言語です。.Netはいつかそれを導入するかもしれませんが、まだそうではありません。CLRはすでにMIをサポートしていますが、すでに述べたように、それ以上の言語構成はまだありません。

それまでは、代わりにProxyオブジェクトと複数のインターフェースに悩まされています:(


39
CLRは複数の実装の継承をサポートしていません。複数のインターフェイスの継承(C#でもサポートされています)のみをサポートしています。
ジョルダン2010

4
@Jordão:完全を期すために、コンパイラがCLRでそのタイプのMIを作成することが可能です。警告はありますが、たとえばCLSに準拠していません。詳細については、この(2004)記事blogs.msdn.com/b/csharpfaq/archive/2004/03/07/…を
dvdvorle

2
@MrHappy:非常に興味深い記事。私は実際にC#の特性構成のいくつかの方法を調査しました。
ジョルダン

10
@MandeepJanjua私はそのようなことを主張しなかった、私は「それをうまく導入するかもしれない」と述べたECMA標準CLRが多重継承のためのIL機構を提供しているという事実が残っています。
IanNorton、2012年

4
FYIの多重継承は悪くありませんし、コードをそれほど複雑にしません。私はそれについて言及するだろうと思っただけです。
Dmitri Nesteruk

214

多重継承をシミュレートする代わりに、コンポジションを使用することを検討してください。あなたは、例えば、クラスが組成物を構成するものを定義するためのインターフェイスを使用することができますISteerableタイプのプロパティを意味しSteeringWheelIBrakable型のプロパティを暗示BrakePedalなど、

それが完了したら、C#3.0に追加された拡張メソッド機能を使用して、これらの暗黙のプロパティのメソッドの呼び出しをさらに簡略化できます。次に例を示します。

public interface ISteerable { SteeringWheel wheel { get; set; } }

public interface IBrakable { BrakePedal brake { get; set; } }

public class Vehicle : ISteerable, IBrakable
{
    public SteeringWheel wheel { get; set; }

    public BrakePedal brake { get; set; }

    public Vehicle() { wheel = new SteeringWheel(); brake = new BrakePedal(); }
}

public static class SteeringExtensions
{
    public static void SteerLeft(this ISteerable vehicle)
    {
        vehicle.wheel.SteerLeft();
    }
}

public static class BrakeExtensions
{
    public static void Stop(this IBrakable vehicle)
    {
        vehicle.brake.ApplyUntilStop();
    }
}


public class Main
{
    Vehicle myCar = new Vehicle();

    public void main()
    {
        myCar.SteerLeft();
        myCar.Stop();
    }
}

13
それがポイントです-このようなアイデアは、構成を容易にします。
Jon Skeet、

9
はい。ただし、メインオブジェクトの一部としてメソッドを本当に必要とするユースケースがあります
David Pierre

9
残念ながら、メンバー変数データには拡張メソッドでアクセスできないため、内部または(ug)パブリックとして公開する必要がありますが、多重継承を解決するには、契約による構成が最良の方法だと思います。
cfeduke、2008年

4
正解です。簡潔で、理解しやすく、非常に役立つイラスト。ありがとうございました!
AJ。

3
myCar電話をかける前に、左ハンドルを終えたかどうかを確認したい場合がありStopます。速度が速すぎるStopときに適用すると、ロールオーバーする可能性がありますmyCar。:D
Devraj Gadhavi 2015年

16

このようなことを可能にするC#ポストコンパイラーを作成しました。

using NRoles;

public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class RFirst : IFirst, Role {
  public void FirstMethod() { Console.WriteLine("First"); }
}

public class RSecond : ISecond, Role {
  public void SecondMethod() { Console.WriteLine("Second"); }
}

public class FirstAndSecond : Does<RFirst>, Does<RSecond> { }

ポストコンパイラーをVisual Studioのビルド後のイベントとして実行できます。

C:\ some_path \ nroles-v0.1.0-bin \ nutate.exe "$(TargetPath)"

同じアセンブリで、次のように使用します。

var fas = new FirstAndSecond();
fas.As<RFirst>().FirstMethod();
fas.As<RSecond>().SecondMethod();

別のアセンブリでは、次のように使用します。

var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();

6

IFirstとISecondの両方を実装する1つの抽象基本クラスを作成し、その基本から継承することもできます。


これはおそらく最良の解決策ですが、必ずしも最良のアイデアとは限りません:p
leppie 08/07/10

1
インターフェースにメソッドを追加するときに、抽象クラスを編集する必要はありませんか?
Rik

Rik:これを一度だけやればいいのに、あなたはどれほど怠惰ですか?
レッピー、2008年

3
@leppie-「インターフェイスの1つにメソッドを追加するたびに、クラスFirstAndSecondも変更する必要があります。」元の質問のこの部分は、このソリューションでは対処されていませんか?
のRik

2
抽象クラスを編集する必要がありますが、それに依存する他のクラスを編集する必要はありません。ドルは、クラスのコレクション全体にカスケードし続けるのではなく、そこで停止します。
Joel Coehoorn、2008年

3

MIは悪くない、それを(真剣に)使用したすべての人がそれを愛し、コードを複雑にすることはありません!少なくとも他の構成要素よりもコードが複雑になることはありません。悪いコードは、MIが画像内にあるかどうかに関係なく、悪いコードです。

とにかく、私が共有したい多重継承の素敵な小さな解決策を手に入れました。http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blogまたは私の署名のリンクをたどることができます... :)


複数の継承を持ち、アップキャストとダウンキャストを保持しながらIDを維持することは可能ですか?私は多重継承の問題のための知っているソリューションは、(あればアイデンティティ保存されないキャスト持つの周りを公転するmyFooタイプのものでありFoo、継承からMooGoo、両方の継承からをBoo、その後、(Boo)(Moo)myFooおよび(Boo)(Goo)myFoo同等ではありません)。アイデンティティを維持するアプローチを知っていますか?
スーパーキャット2013

1
リンクをたどるにはweb.archive.orgなどを使用できますが、ここで元の質問で提供されたソリューションの正確な説明であることがわかります。
MikeBeaton

2

私自身の実装では、MIにクラス/インターフェイスを使用することは、「良い形」ではありますが、必要な関数呼び出しの数だけにすべての継承を設定する必要があるため、複雑になりがちであることがわかりました。文字通り何十回も冗長に実行する必要がありました。

代わりに、静的な「関数を呼び出す関数を呼び出す」関数を、一種のOOPの置き換えとして、さまざまなモジュールのバリエーションで簡単に作成する方が簡単でした。私が取り組んでいた解決策は、RPGの「スペルシステム」でした。この例では、例に示されているように、エフェクトを大幅に組み合わせて関数呼び出しを行い、コードを書き直さずにさまざまなスペルを提供する必要があります。

スペルロジックのインスタンスは必ずしも必要ないため、ほとんどの関数は静的になりました。一方、クラスの継承では、静的な仮想キーワードや抽象キーワードを使用することもできません。インターフェイスはそれらをまったく使用できません。

コーディングは、この方法のIMOの方がはるかに高速でクリーンです。関数を実行するだけで、継承されたプロパティが必要ない場合は、関数を使用します。


2

C#8を使用すると、インターフェースメンバーのデフォルト実装を介して実質的に複数の継承が可能になります。

interface ILogger
{
    void Log(LogLevel level, string message);
    void Log(Exception ex) => Log(LogLevel.Error, ex.ToString()); // New overload
}

class ConsoleLogger : ILogger
{
    public void Log(LogLevel level, string message) { ... }
    // Log(Exception) gets default implementation
}

4
はい。ただし、上記では実行できないことに注意してください。new ConsoleLogger().Log(someEception)単に機能しないILoggerため、デフォルトのインターフェイスメソッドを使用するには、オブジェクトをに明示的にキャストする必要があります。そのため、その有用性はいくぶん制限されています。
Dmitri Nesteruk

1

IFirstとISecondのメソッドがIFirstとISecondのコントラクトとのみ対話する必要があるという制限に耐えられる場合(例のように)...拡張メソッドを使用して要求することを実行できます。実際には、これはまれなケースです。

public interface IFirst {}
public interface ISecond {}

public class FirstAndSecond : IFirst, ISecond
{
}

public static MultipleInheritenceExtensions
{
  public static void First(this IFirst theFirst)
  {
    Console.WriteLine("First");
  }

  public static void Second(this ISecond theSecond)
  {
    Console.WriteLine("Second");
  }
}

///

public void Test()
{
  FirstAndSecond fas = new FirstAndSecond();
  fas.First();
  fas.Second();
}

基本的な考え方は、必要な実装をインターフェースで定義することです...この必要なものは、拡張メソッドでの柔軟な実装をサポートする必要があります。「インターフェースにメソッドを追加する」必要があるときはいつでも、代わりに拡張メソッドを追加します。


1

はい。インターフェイスを使用するのは面倒です。クラスにメソッドを追加するときはいつでも、インターフェイスに署名を追加する必要があるからです。また、多数のメソッドを持つクラスがすでにあり、そのためのインターフェイスがない場合はどうなりますか?継承元となるすべてのクラスのインターフェイスを手動で作成する必要があります。そして最悪のことは、子クラスが複数のインターフェイスから継承する場合、子クラスのインターフェイスのすべてのメソッドを実装する必要があることです。

Facadeデザインパターンに従うことで、アクセサを使用して複数のクラスからの継承をシミュレートできます。継承が必要なクラス内で{get; set;}を使用してクラスをプロパティとして宣言します。すべてのパブリックプロパティとメソッドはそのクラスからのものであり、子クラスのコンストラクターでは親クラスをインスタンス化します。

例えば:

 namespace OOP
 {
     class Program
     {
         static void Main(string[] args)
         {
             Child somechild = new Child();
             somechild.DoHomeWork();
             somechild.CheckingAround();
             Console.ReadLine();
         }
     }

     public class Father 
     {
         public Father() { }
         public void Work()
         {
             Console.WriteLine("working...");
         }
         public void Moonlight()
         {
             Console.WriteLine("moonlighting...");
         }
     }


     public class Mother 
     {
         public Mother() { }
         public void Cook()
         {
             Console.WriteLine("cooking...");
         }
         public void Clean()
         {
             Console.WriteLine("cleaning...");
         }
     }


     public class Child 
     {
         public Father MyFather { get; set; }
         public Mother MyMother { get; set; }

         public Child()
         {
             MyFather = new Father();
             MyMother = new Mother();
         }

         public void GoToSchool()
         {
             Console.WriteLine("go to school...");
         }
         public void DoHomeWork()
         {
             Console.WriteLine("doing homework...");
         }
         public void CheckingAround()
         {
             MyFather.Work();
             MyMother.Cook();
         }
     }


 }

この構造では、子クラスは、親クラスのインスタンスを継承する多重継承をシミュレートして、親クラスと親クラスのすべてのメソッドとプロパティにアクセスできます。まったく同じではありませんが、実用的です。


2
私は最初の段落に同意しません。EVERYクラスで必要なメソッドのシグネチャのみをインターフェイスに追加します。ただし、クラスに必要なだけメソッドを追加できます。また、右クリックして、インターフェースを抽出する作業を簡単にする抽出インターフェースがあります。最後に、あなたの例は継承ではありません(多重またはその他)、しかしそれは構成の素晴らしい例です。悲しいかな、インターフェースを使用して、コンストラクター/プロパティインジェクションを使用するDI / IOCのデモンストレーションも行うと、よりメリットがあります。私は反対票を投じませんが、申し訳ありませんが良い答えだとは思いません。
フランシスロジャース2014

1
1年後のこのスレッドを振り返ると、インターフェイスに署名を追加せずに、クラスに必要なだけメソッドを追加できることに同意しますが、これではインターフェイスが不完全になります。さらに、IDEで右クリック-抽出インターフェイスを見つけることができませんでした。おそらく何か不足していました。しかし、私の大きな懸念は、インターフェイスで署名を指定する場合、継承するクラスがその署名を実装する必要があることです。これは二重の作業であり、コードの重複につながる可能性があると思います。
ヨギ

EXTRACTのINTERFACE:クラス署名の上に右クリックし、インタフェースを抽出... VS2015同じプロセス内除いあなたは右クリックする必要がありますが、その後、選択したQuick Actions and Refactorings...これを知っている必要があり、それはあなたに多くの時間を節約します
Chef_Code

1

私たちは皆、このとのインタフェースパスダウン見出しているように見えるが、明らかな他の可能性は、ここでは、... OOPを行うことになっている何をすべきか、そしてあなたの継承ツリーを構築することで、デザインはすべてであるこの何クラス(されていません約?)

class Program
{
    static void Main(string[] args)
    {
        human me = new human();
        me.legs = 2;
        me.lfType = "Human";
        me.name = "Paul";
        Console.WriteLine(me.name);
    }
}

public abstract class lifeform
{
    public string lfType { get; set; }
}

public abstract class mammal : lifeform 
{
    public int legs { get; set; }
}

public class human : mammal
{
    public string name { get; set; }
}

この構造は、再利用可能なコードブロックを提供します。確かに、OOPコードはどのように記述する必要がありますか?

この特定のアプローチが法案に適合しない場合は、必要なオブジェクトに基づいて新しいクラスを作成するだけです...

class Program
{
    static void Main(string[] args)
    {
        fish shark = new fish();
        shark.size = "large";
        shark.lfType = "Fish";
        shark.name = "Jaws";
        Console.WriteLine(shark.name);
        human me = new human();
        me.legs = 2;
        me.lfType = "Human";
        me.name = "Paul";
        Console.WriteLine(me.name);
    }
}

public abstract class lifeform
{
    public string lfType { get; set; }
}

public abstract class mammal : lifeform 
{
    public int legs { get; set; }
}

public class human : mammal
{
    public string name { get; set; }
}

public class aquatic : lifeform
{
    public string size { get; set; }
}

public class fish : aquatic
{
    public string name { get; set; }
}

0

多重継承は、通常、解決するよりも多くの問題を引き起こすものの1つです。C ++では、自分自身をぶら下げるのに十分なロープを与えるパターンに適合しますが、JavaとC#は、オプションを与えないというより安全な方法を選択しました。最大の問題は、継承先が実装していない同じシグネチャを持つメソッドを持つ複数のクラスを継承する場合にどうするかです。どのクラスのメソッドを選択する必要がありますか?それともコンパイルしないでください?一般的に、多重継承に依存しないほとんどのものを実装する別の方法があります。


8
MIをC ++で判断しないでください。これは、OOPをPHPで、自動車をピントスで判断するようなものです。その問題は簡単に解決できます。Eiffelでは、クラスから継承するときに、継承するメソッドも指定する必要があり、名前を変更できます。そこには曖昧さや驚きもありません。
イェルクWミッターク

2
@mP:いいえ、Eiffelは真の多重実装継承を提供します。名前を変更しても、継承チェーンが失われるわけではなく、クラスのキャスト可能性が失われるわけでもありません。
アベル

0

XがYから継承する場合、2つのやや直交する効果があります。

  1. YはXにデフォルトの機能を提供するため、XのコードにはYとは異なるものを含めるだけで済みます。
  2. Yが予想されるほとんどすべての場所で、代わりにXを使用できます。

継承は両方の機能を提供しますが、一方が他方なしで使用できる状況を想像することは難しくありません。私が知っている.net言語には、1つ目を2つ目なしで直接実装する方法はありませんが、直接使用されることのない基本クラスを定義し、何も追加せずにそこから直接継承する1つ以上のクラスを作成することで、このような機能を取得できます新規(そのようなクラスはすべてのコードを共有できますが、相互に置換できません)。ただし、CLR準拠の言語では、最初の(メンバーの再利用)がなくても、インターフェイスの2番目の機能(代替可能性)を提供するインターフェイスを使用できます。


0

私はそれが許可されていなくても、私が知っていることを知っています。

class a {}
class b : a {}
class c : b {}

私の場合のように、私はこのクラスを実行したいと思いましたb:フォーム(そうです、windows.forms)クラスc:b {}

関数の半分は同一であり、インターフェイスを使用すると、uはそれらをすべて書き換える必要があります


1
あなたの例は多重継承を描いていません、それであなたはどんな問題を解決しようとしていますか?真の多重継承の例が表示class a : b, cされます(必要なコントラクトホールの実装)。おそらくあなたの例は単純化されすぎているのでしょうか?
M.バブコック2013年

0

多重継承(MI)の問題が時々浮かび上がるので、構成パターンのいくつかの問題に対処するアプローチを追加したいと思います。

私は上に構築IFirstISecondFirstSecondFirstAndSecondそれは質問で提示されたように、アプローチ。IFirstインターフェイス/ MIベースクラスの数に関係なくパターンは同じままなので、サンプルコードをに減らします。

MI Firstを使用Secondし、両方とも同じ基本クラスから派生し、からのBaseClassパブリックインターフェイス要素のみを使用するとしますBaseClass

これはBaseClassFirstおよびのSecond実装にコンテナ参照を追加することで表現できます。

class First : IFirst {
  private BaseClass ContainerInstance;
  First(BaseClass container) { ContainerInstance = container; }
  public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); } 
}
...

保護されたインターフェイス要素BaseClassが参照される場合First、またはSecondMIの抽象クラスになる場合、サブクラスがいくつかの抽象パーツを実装する必要があるため、状況はさらに複雑になります。

class BaseClass {
  protected void DoStuff();
}

abstract class First : IFirst {
  public void FirstMethod() { DoStuff(); DoSubClassStuff(); }
  protected abstract void DoStuff(); // base class reference in MI
  protected abstract void DoSubClassStuff(); // sub class responsibility
}

C#を使用すると、ネストされたクラスは、それを含むクラスの保護された/プライベートな要素にアクセスできるため、First実装から抽象ビットをリンクするために使用できます。

class FirstAndSecond : BaseClass, IFirst, ISecond {
  // link interface
  private class PartFirst : First {
    private FirstAndSecond ContainerInstance;
    public PartFirst(FirstAndSecond container) {
      ContainerInstance = container;
    }
    // forwarded references to emulate access as it would be with MI
    protected override void DoStuff() { ContainerInstance.DoStuff(); }
    protected override void DoSubClassStuff() { ContainerInstance.DoSubClassStuff(); }
  }
  private IFirst partFirstInstance; // composition object
  public FirstMethod() { partFirstInstance.FirstMethod(); } // forwarded implementation
  public FirstAndSecond() {
    partFirstInstance = new PartFirst(this); // composition in constructor
  }
  // same stuff for Second
  //...
  // implementation of DoSubClassStuff
  private void DoSubClassStuff() { Console.WriteLine("Private method accessed"); }
}

かなりのボイラープレートが関係していますが、FirstMethodとSecondMethodの実際の実装が十分に複雑で、アクセスされるプライベート/保護されたメソッドの量が中程度である場合、このパターンは多重継承の欠如を克服するのに役立ちます。


0

これはLawrence Wenhamの回答のとおりですが、ユースケースによっては、改善になる場合と改善されない場合があります。セッターは必要ありません。

public interface IPerson {
  int GetAge();
  string GetName();
}

public interface IGetPerson {
  IPerson GetPerson();
}

public static class IGetPersonAdditions {
  public static int GetAgeViaPerson(this IGetPerson getPerson) { // I prefer to have the "ViaPerson" in the name in case the object has another Age property.
    IPerson person = getPerson.GetPersion();
    return person.GetAge();
  }
  public static string GetNameViaPerson(this IGetPerson getPerson) {
    return getPerson.GetPerson().GetName();
  }
}

public class Person: IPerson, IGetPerson {
  private int Age {get;set;}
  private string Name {get;set;}
  public IPerson GetPerson() {
    return this;
  }
  public int GetAge() {  return Age; }
  public string GetName() { return Name; }
}

これで、人を取得する方法を知っているすべてのオブジェクトがIGetPersonを実装できるようになり、GetAgeViaPerson()メソッドとGetNameViaPerson()メソッドが自動的に追加されます。この時点から、基本的にすべてのPersonコードはIPGetではなくIGetPersonに入りますが、両方に入る必要がある新しいivarは除きます。そのようなコードを使用する場合、IGetPersonオブジェクト自体が実際にIPersonであるかどうかを気にする必要はありません。


0

これは今では可能なpartialクラスのトラフであり、各クラスは独自にクラスを継承することができ、最終的なオブジェクトがすべての基本クラスを継承するようにします。詳細については、こちらをご覧ください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.