234

多重継承は良くない (ソースが複雑になる) ため、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 つのクラスに結合するのが最も簡単な解決策です。

競合を避けるために、これはコード生成によって行われ、結果は後で変更される可能性がありますが、これを手で入力するのは本当に面倒です。

4

14 に答える 14

225

多重継承をシミュレートする代わりに、コンポジションを使用することを検討してください。インターフェイスを使用して、コンポジションを構成するクラスを定義できます。たとえばISteerable、 type のプロパティを意味する、 typeSteeringWheelIBrakableプロパティを意味する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();
    }
}
于 2008-10-07T13:13:31.330 に答える
143

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

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

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

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

それまでは、代わりにプロキシオブジェクトと複数のインターフェイスで立ち往生しています:(

于 2009-11-08T07:09:29.780 に答える
17

このようなことを可能にする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();
于 2011-05-14T14:48:48.753 に答える
6

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

于 2008-10-07T13:09:28.763 に答える
2

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

代わりに、一種の OOP 置換として、さまざまなモジュラー型で静的な「関数を呼び出す関数を呼び出す関数」を単純に作成する方が簡単でした。私が取り組んでいた解決策は、例が示すように、コードを書き直すことなく非常に多様な呪文を与えるために、効果が関数呼び出しを大幅に組み合わせて一致させる必要がある RPG の「呪文システム」でした。

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

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

于 2017-12-14T23:26:26.520 に答える
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();
}

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

于 2008-10-07T13:23:19.000 に答える
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();
         }
     }


 }

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

于 2014-01-15T18:33:58.407 に答える
0

許可されていないなどの理由で、実際に必要になる場合があります。

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

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

関数の半分が同一であり、インターフェースuを使用してそれらをすべて書き直す必要があるためです。

于 2012-03-28T11:35:53.613 に答える
0

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

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

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

于 2011-08-24T14:40:29.873 に答える
0

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

于 2008-10-07T13:31:55.503 に答える
0

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

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

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

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

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

BaseClassから保護されたインターフェイス要素が参照される場合、またはFirstMISecondで抽象クラスになる場合、それらのサブクラスがいくつかの抽象部分を実装する必要がある場合、事態はより複雑になります。

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 の実際の実装が十分に複雑で、アクセスされるプライベート/保護されたメソッドの量が中程度である場合、このパターンは多重継承の欠如を克服するのに役立つ可能性があります。

于 2015-10-12T13:04:18.593 に答える