web-dev-qa-db-ja.com

コンストラクタを継承する方法は?

Imagine多くのコンストラクターと仮想メソッドを持つ基本クラス

public class Foo
{
   ...
   public Foo() {...}
   public Foo(int i) {...}
   ...
   public virtual void SomethingElse() {...}
   ...
}

そして今、私は仮想メソッドをオーバーライドする子孫クラスを作成したい:

public class Bar : Foo 
{
   public override void SomethingElse() {...}
}

そして、さらにいくつかのことを行う別の子孫:

public class Bah : Bar
{
   public void DoMoreStuff() {...}
}

すべてのコンストラクターをFooからBar and Bahにコピーする必要が本当にありますか? Fooでコンストラクターシグネチャを変更した場合、Bar and Bahでそれを更新する必要がありますか?

コンストラクターを継承する方法はありませんか?コードの再利用を促す方法はありませんか?

181
Ian Boyd

はい、各派生に意味のあるコンストラクターを実装し、baseキーワードを使用してそのコンストラクターを適切な基本クラスに転送するか、thisキーワードを使用してコンストラクターを同じクラスの別のコンストラクターに転送する必要があります。

コンパイラーがコンストラクターの継承について仮定した場合、オブジェクトのインスタンス化方法を適切に判断することはできません。ほとんどの場合、コンストラクターの数が非常に多い理由を検討し、基本クラスでコンストラクターを1つまたは2つに減らすことを検討する必要があります。派生クラスは、nullなどの定数値を使用してそれらの一部をマスクし、コンストラクターを介して必要なクラスのみを公開できます。

更新

C#4では、既定のパラメーター値を指定し、名前付きパラメーターを使用して、構成ごとに1つのコンストラクターを持たせるのではなく、単一のコンストラクターが複数の引数構成をサポートできるようにすることができます。

122
Jeff Yates

387コンストラクター??それがあなたの主な問題です。代わりにこれはどうですか?

public Foo(params int[] list) {...}
70
Kon

はい、すべての387コンストラクターをコピーする必要があります。それらをリダイレクトすることで再利用できます:

  public Bar(int i): base(i) {}
  public Bar(int i, int j) : base(i, j) {}

しかし、それはあなたができる最善です。

42
James Curran

残念ながら、コンパイラーに明白なことを言わざるを得ません。

Subclass(): base() {}
Subclass(int x): base(x) {}
Subclass(int x,y): base(x,y) {}

私は12のサブクラスで3つのコンストラクターを実行するだけでよいので、大したことではありませんが、長い間記述しなくても済むようになった後、すべてのサブクラスでそれを繰り返すことはあまり好きではありません。それには正当な理由があると確信していますが、この種の制限を必要とする問題に遭遇したことはないと思います。

34
jackbw

同じレベルの継承でコンストラクターを他のコンストラクターにリダイレクトすることもできることを忘れないでください:

public Bar(int i, int j) : this(i) { ... }
                            ^^^^^
27
dviljoen

別の簡単な解決策は、プロパティとしてパラメーターを含む構造体または単純なデータクラスを使用することです。そうすれば、すべてのデフォルト値と動作を事前に設定して、「パラメータークラス」を単一のコンストラクターパラメーターとして渡すことができます。

public class FooParams
{
    public int Size...
    protected myCustomStruct _ReasonForLife ...
}
public class Foo
{
    private FooParams _myParams;
    public Foo(FooParams myParams)
    {
          _myParams = myParams;
    }
}

これにより、(時には)複数のコンストラクターの混乱が回避され、強力な型指定、デフォルト値、およびパラメーター配列では提供されないその他の利点が得られます。また、Fooから継承したものは必要に応じてFooParamsに到達したり、FooParamsに追加したりすることもできるため、先への移行が容易になります。それでもコンストラクタをコピーする必要がありますが、常に(ほとんどの場合)常に(一般的な規則として)常に(少なくとも今のところ)1つのコンストラクタが必要です。

public class Bar : Foo
{
    public Bar(FooParams myParams) : base(myParams) {}
}

オーバーロードされたInitailize()とClass Factory Patternのアプローチのほうが本当に好きですが、スマートコンストラクターが必要な場合もあります。ちょっとした考え。

10
Exocubic

Fooはクラスなので、仮想のオーバーロードされたInitialise()メソッドを作成できませんか?その後、サブクラスで利用でき、さらに拡張可能ですか?

public class Foo
{
   ...
   public Foo() {...}

   public virtual void Initialise(int i) {...}
   public virtual void Initialise(int i, int i) {...}
   public virtual void Initialise(int i, int i, int i) {...}
   ... 
   public virtual void Initialise(int i, int i, ..., int i) {...}

   ...

   public virtual void SomethingElse() {...}
   ...
}

これには、デフォルトのプロパティ値がたくさんあり、多くヒットしない限り、パフォーマンスコストは高くありません。

7
Keith
public class BaseClass
{
    public BaseClass(params int[] parameters)
    {

    }   
}

public class ChildClass : BaseClass
{
    public ChildClass(params int[] parameters)
        : base(parameters)
    {

    }
}
6
Pavlo Neyman

個人的には、これはMicrosoft側の間違いだと思います。プログラマが基本クラスのコンストラクタ、メソッド、およびプロパティの可視性をオーバーライドし、コンストラクタが常に継承されるようにする必要があります。

この方法では、必要なすべてのコンストラクターを追加する代わりに、DONTが必要とするコンストラクターを単純にオーバーライドします(可視性が低い-つまりPrivate)。 Delphiはこの方法でそれを行いますが、私はそれが恋しいです。

たとえば、System.IO.StreamWriterクラスをオーバーライドする場合、7つのコンストラクターすべてを新しいクラスに追加する必要があります。コメントが必要な場合は、ヘッダーXMLで各コメントをコメントする必要があります。さらに悪いことに、メタデータビューはXMLコメントを適切なXMLコメントとして表示しないため、行ごとにコピーして貼り付ける必要があります。ここでマイクロソフトは何を考えていましたか?

実際に、メタデータコードを貼り付けることができる小さなユーティリティを作成しました。このユーティリティは、オーバーライドされた可視性を使用してXMLコメントに変換します。

5

FooのすべてのコンストラクターをBarおよびBahに本当にコピーする必要がありますか?そして、Fooのコンストラクターシグネチャを変更した場合、BarおよびBahでそれを更新する必要がありますか?

はい、コンストラクターを使用してインスタンスを作成する場合。

コンストラクターを継承する方法はありませんか?

いや。

コードの再利用を促す方法はありませんか?

さて、コンストラクターを継承することは良いことか悪いことか、それがコードの再利用を促進するかどうかには触れません。なぜなら、コンストラクターがなく、コンストラクターを取得しないからです。 :-)

しかし、2014年の現在のC#では、汎用のcreateメソッドを代わりに使用することで、非常に多くの何かlike継承されたコンストラクターを取得できます。ベルトに入れておくと便利なツールになりますが、軽く手を伸ばすことはできません。数百の派生クラスで使用される基本型のコンストラクターに何かを渡す必要に直面したとき、私は最近それに到達しました(最近まで、基底は引数を必要としなかったので、デフォルトのコンストラクターは問題ありませんでしたクラスはコンストラクターをまったく宣言せず、自動的に提供されるコンストラクターを取得しました。

次のようになります。

// In Foo:
public T create<T>(int i) where: where T : Foo, new() {
    T obj = new T();
    // Do whatever you would do with `i` in `Foo(i)` here, for instance,
    // if you save it as a data member;  `obj.dataMember = i;`
    return obj;
}

つまり、引数がゼロのコンストラクターを持つcreateの任意のサブタイプである型パラメーターを使用して、汎用Foo関数を呼び出すことができます。

次に、Bar b new Bar(42)を行う代わりに、次のようにします。

var b = Foo.create<Bar>(42);
// or
Bar b = Foo.create<Bar>(42);
// or
var b = Bar.create<Bar>(42); // But you still need the <Bar> bit
// or
Bar b = Bar.create<Bar>(42);

そこにcreateメソッドがFooに直接あることを示しましたが、設定する情報をそのファクトリクラスで設定できる場合は、もちろん何らかのファクトリクラスにすることもできます。

わかりやすくするために、名前createは重要ではありません。makeThingyまたはその他の名前でも構いません。

完全な例

using System.IO;
using System;

class Program
{
    static void Main()
    {
        Bar b1 = Foo.create<Bar>(42);
        b1.ShowDataMember("b1");

        Bar b2 = Bar.create<Bar>(43); // Just to show `Foo.create` vs. `Bar.create` doesn't matter
        b2.ShowDataMember("b2");
    }

    class Foo
    {
        public int DataMember { get; private set; }

        public static T create<T>(int i) where T: Foo, new()
        {
            T obj = new T();
            obj.DataMember = i;
            return obj;
        }
    }

    class Bar : Foo
    {
        public void ShowDataMember(string prefix)
        {
            Console.WriteLine(prefix + ".DataMember = " + this.DataMember);
        }
    }
}
4
T.J. Crowder

問題は、BarとBahが387コンストラクターをコピーする必要があるということではなく、Fooには387コンストラクターがあることです。 Fooは明らかに多くのことを行います-リファクタリングは迅速です!また、コンストラクターに値を設定する本当に正当な理由がない限り(パラメーターなしのコンストラクターを提供する場合はおそらくそうしません)、プロパティの取得/設定を使用することをお勧めします。

3

いいえ、387個すべてのコンストラクターをBar and Bahにコピーする必要はありません。 BarとBahには、Fooで定義する数に関係なく、必要なだけのコンストラクタを作成できます。たとえば、Fooの212番目のコンストラクターでFooを構築するBarコンストラクターを1つだけ選択することができます。

はい、BarまたはBahが依存するFooで変更するコンストラクターは、それに応じてBarおよびBahを変更する必要があります。

いいえ、.NETにはコンストラクタを継承する方法はありません。ただし、サブクラスのコンストラクター内で基本クラスのコンストラクターを呼び出すか、定義した仮想メソッド(Initialize()など)を呼び出すことで、コードを再利用できます。

2
C. Dragon 76

C++仮想コンストラクターイディオム のバージョンを適応できる場合があります。私の知る限り、C#は共変の戻り値型をサポートしていません。私はそれが多くの人々のウィッシュリストに載っていると信じています。

1
Greg D

コンストラクタが多すぎるということは、デザインが壊れている兆候です。コンストラクターが少なく、プロパティを設定する機能を備えたクラスを改善します。プロパティを本当に制御する必要がある場合は、同じ名前空間のファクトリを検討し、プロパティセッターを内部にします。ファクトリーにクラスのインスタンス化とそのプロパティの設定方法を決定させます。ファクトリーは、オブジェクトを適切に構成するために必要な数のパラメーターを取るメソッドを持つことができます。

0
tvanfosson