web-dev-qa-db-ja.com

C#での多重継承

多重継承は悪いので(ソースがより複雑になります)、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つのクラスに組み合わせることが最も簡単な解決策になります。

衝突を避けるために、結果が後で変更される可能性があるコード生成によってこれが行われるかもしれませんが、これを手でタイプすることはお尻の純粋な苦痛です。

202
Martin

多重継承は悪いので(ソースがより複雑になります)、C#はそのようなパターンを直接提供しません。しかし、時にはそれはこの能力を持っていると便利だろう。

C#と.net CLRはMIを実装していません。なぜなら、それらがC#、VB.netおよび他の言語の間でどのように相互運用するかを結論づけていないからです。

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

Perlは、MIが機能し、うまく機能する場所で、私がこれまでに作業した唯一の言語です。 .Netはいつかそれを紹介するかもしれませんが、CLRはすでにMIをサポートしていますが、私が言ったように、それを超える言語構成はまだありません。

それまでは、Proxyオブジェクトと複数のインターフェイスを代わりに使用することになります。

116
IanNorton

多重継承をシミュレートするのではなく、compositionを使用することを検討してください。たとえば、ISteerableSteeringWheel型のプロパティを意味し、IBrakableBrakePedal型のプロパティを意味します。

それが済んだら、C#3.0に追加された Extension Methods 機能を使用して、これらの暗黙のプロパティでメソッドを呼び出すことをさらに簡単にすることができます。

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();
    }
}
203
Chris Wenham

私もこれが欲しいです - それは私が個人的にミックスインと呼んでいるものですが、私はそれが過負荷の用語であることを認識していますが。私は特定のメソッドのための私自身の実装を提供するオプションで、インターフェースを実装するために使われる変数を指定できるようにしたいです。

私は これについてより詳細にブログを書いている - それは継承の観点からそれが意味することができるものの慎重な誇張の文脈の中では。

これがC#コンパイラに実装できなかった理由はわかりませんが、言語が複雑になるもう1つの理由は...

14
Jon Skeet

このようなことを可能にする 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のpost-build-eventとして実行できます。

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();
13
Jordão

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

5
Joel Coehoorn

MIは悪くありません、それを(真剣に)使った人はみんなそれが大好きですし、コードを複雑にすることもありません!少なくとも他の構成要素を超えないようにすると、コードが複雑になる可能性があります。 MIが画像に含まれているかどうかにかかわらず、不正コードは不正コードです。

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

3
Thomas Hansen

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

ファサードのデザインパターンに従うことで、アクセサを使って複数のクラスからの継承をシミュレートすることができます。継承する必要があるクラス内で{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();
         }
     }


 }

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

1
Yogi

もしあなたが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
Amy B

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

0
tloach

多重継承(MI)の問題が時々現れるので、私は構成パターンに関するいくつかの問題に取り組むアプローチを加えたいと思います。

質問で示されているように、私はIFirstname__、ISecondname __、Firstname__、Secondname__、FirstAndSecondname__のアプローチに基づいています。サンプルコードをIFirstname__に減らします。これは、インターフェイス/ MI基本クラスの数に関係なく、パターンは同じままであるためです。

MIでは、Firstname__とSecondname__はどちらも、BaseClassname__のパブリックインターフェイス要素のみを使用して、同じ基本クラスBaseClassname__から派生すると想定します。

これは、BaseClassname__およびFirstname__の実装でSecondname__へのコンテナ参照を追加することで表現できます。

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

BaseClassname__からの保護されたインターフェース要素が参照されるとき、またはFirstname__およびSecondname__がMIの抽象クラスになるとき、それらのサブクラスがいくつかの抽象部分を実装することを要求するとき、事はより複雑になります。

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#では、入れ子になったクラスがそれを含むクラスの保護された/非公開の要素にアクセスできるため、これを使用してFirstname__実装からの抽象ビットをリンクできます。

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
grek40

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

代わりに、一種のOOP置換として、単純に静的な「関数を呼び出す関数を呼び出す関数」をさまざまなモジュール形式で作成する方が簡単でした。私が取り組んでいた解決策はRPGのための「スペルシステム」で、書き直すことなく極端な種類のスペルを与えるためにエフェクトは重く関数を呼び出す必要がありました。例が示しているように、コード。

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

このようにIMOを使うとコーディングはずっと速くてきれいになります。関数を実行しているだけで、継承されたプロパティが必要ない場合は、関数を使用してください。

0
hydrix

これは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コードはIPetonではなくIGetPersonに入りますが、新しいivar以外は両方に入ります。そして、そのようなコードを使用する際には、あなたのIGetPersonオブジェクトがそれ自体実際にIPersonであるかどうかについて心配する必要はありません。

0

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

  1. YはXにデフォルトの機能を提供するので、XのコードにはYとは異なるものだけを含める必要があります。
  2. ほとんどどこにでもYが期待されるでしょう、代わりにXが使用されるかもしれません。

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

0
supercat

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
}
0
Ludmil Tinkov

私たち全員がこれでインターフェースのパスを下っているように見えますが、ここで明らかな他の可能性は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 codeはどのように書かれるべきですか?

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

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
Paul

許可されていないとしても、知っていることはわかっています。

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

私の場合のように、私はこのクラスbをやりたかった:Form(はい、windows.forms)クラスc:b {}

原因関数の半分が同一で、インタフェースuを使ってそれらすべてを書き直さなければならない

0
ariel