質問
想像してください 多くのコンストラクターと仮想メソッドを含む基本クラス
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とBahにコピーする必要が本当にありますか?そして、Fooでコンストラクターシグネチャを変更した場合、Bar and Bahでそれを更新する必要がありますか?
コンストラクタを継承する方法はありませんか?コードの再利用を促す方法はありませんか?
解決
はい、各派生に意味のあるコンストラクターを実装し、 base
キーワードを使用して、そのコンストラクターを適切な基本クラスまたは this
コンストラクタを同じクラスの別のコンストラクタに導くキーワード。
コンパイラがコンストラクターの継承について仮定した場合、オブジェクトがどのようにインスタンス化されるかを適切に判断することはできません。ほとんどの場合、コンストラクターの数が非常に多い理由を検討し、基本クラスでコンストラクターを1つまたは2つに減らすことを検討する必要があります。派生クラスは、 null
などの定数値を使用してそれらの一部をマスクし、コンストラクターを通じて必要なクラスのみを公開できます。
更新
C#4では、デフォルトのパラメーター値を指定し、名前付きパラメーターを使用して、構成ごとに1つのコンストラクターを持たせるのではなく、単一のコンストラクターが複数の引数構成をサポートできるようにします。
他のヒント
387コンストラクタ??それがあなたの主な問題です。代わりにこれはどうですか?
public Foo(params int[] list) {...}
はい、すべての387コンストラクターをコピーする必要があります。リダイレクトすることで再利用できます:
public Bar(int i): base(i) {}
public Bar(int i, int j) : base(i, j) {}
しかし、それはあなたができる最善のことです。
残念ながら、コンパイラーに明白なことを言わざるを得ません:
Subclass(): base() {}
Subclass(int x): base(x) {}
Subclass(int x,y): base(x,y) {}
12個のサブクラスで3つのコンストラクターを実行するだけでよいので、大したことではありませんが、それほど長く書く必要がないことに慣れてから、すべてのサブクラスでそれを繰り返すのはあまり好きではありません。正当な理由があると確信していますが、この種の制限を必要とする問題に遭遇したことはないと思います。
コンストラクタを同じ継承レベルの他のコンストラクタにリダイレクトできることも忘れないでください:
public Bar(int i, int j) : this(i) { ... }
^^^^^
もう1つの簡単な解決策は、プロパティとしてパラメーターを含む構造体または単純なデータクラスを使用することです。そうすれば、すべてのデフォルト値と動作を事前に設定して、「パラメータクラス」を渡すことができます。単一のコンストラクタパラメータとして:
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アプローチが本当に好きですが、時にはスマートコンストラクターが必要な場合もあります。ただの考え。
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() {...}
...
}
デフォルトのプロパティ値がたくさんあり、頻繁にヒットしない限り、これによりパフォーマンスコストが高くなることはありません。
public class BaseClass
{
public BaseClass(params int[] parameters)
{
}
}
public class ChildClass : BaseClass
{
public ChildClass(params int[] parameters)
: base(parameters)
{
}
}
個人的にはこれはMicrosoft側の誤りだと思います。プログラマが基本クラスのコンストラクタ、メソッド、プロパティの可視性をオーバーライドし、コンストラクタが常に継承されるようにする必要があります。
この方法では、必要なすべてのコンストラクターを追加する代わりに、DONTが必要とするコンストラクターを単純にオーバーライドします(可視性が低い-つまりPrivate)。 Delphiはこの方法でそれを行いますが、私はそれが恋しいです。
たとえば、System.IO.StreamWriterクラスをオーバーライドする場合、7つのコンストラクターすべてを新しいクラスに追加する必要があります。コメントが必要な場合は、ヘッダーXMLで各コメントをコメントする必要があります。さらに悪いことに、メタデータビューはXMLコメントを適切なXMLコメントとして表示しないため、行ごとにコピーして貼り付ける必要があります。マイクロソフトはここで何を考えていましたか?
実際には、メタデータコードを貼り付けることができる小さなユーティリティを作成しました。このユーティリティは、オーバーライドされた可視性を使用してXMLコメントに変換します。
本当にすべてのコンストラクタを
Foo
からBar
とBah
にコピーする必要がありますか?そして、Foo
でコンストラクタ署名を変更した場合、Bar
およびBah
でそれを更新する必要がありますか?
はい、コンストラクターを使用してインスタンスを作成する場合。
コンストラクタを継承する方法はありませんか?
いいえ。
コードの再利用を促進する方法はありませんか?
まあ、継承コンストラクタは良いものか悪いものか、それがコードの再利用を促進するかどうかについては、それらを持っていないので取得しませんので、説明しません。 :-)
ここ2014年、現在のC#では、代わりに汎用の create
メソッドを使用することで、 like 継承されたコンストラクタを取得できます。ベルトに入れておくと便利なツールになりますが、軽く手を伸ばすことはできません。数百の派生クラスで使用されるベース型のコンストラクターに何かを渡す必要があるときに直面したとき、私は最近それに到達しました(最近まで、ベースは引数を必要としなかったため、デフォルトのコンストラクターは問題ありませんでした8212;派生クラスはコンストラクターをまったく宣言せず、自動的に提供されるコンストラクターを取得しました。
次のようになります:
// 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;
}
つまり、引数のないコンストラクターを持つ Foo
のサブタイプである型パラメーターを使用して、汎用の create
関数を呼び出すことができます。
次に、 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);
}
}
}
問題は、BarとBahが387個のコンストラクターをコピーする必要があるということではなく、Fooが387個のコンストラクターをコピーすることです。 Fooは明らかに多くのことを行います-リファクタリングは迅速です!また、コンストラクターに値を設定する本当に正当な理由がない限り(パラメーターなしのコンストラクターを提供する場合はおそらくそうしません)、プロパティの取得/設定を使用することをお勧めします。
いいえ、すべての387コンストラクターをBarとBahにコピーする必要はありません。 BarとBahには、Fooで定義する数に関係なく、必要なだけのコンストラクタを作成できます。たとえば、Fooの212番目のコンストラクターでFooを構築するBarコンストラクターを1つだけ選択することができます。
はい、FooでBarまたはBahが依存するコンストラクタを変更する場合、それに応じてBarおよびBahを変更する必要があります。
いいえ、.NETにはコンストラクターを継承する方法はありません。ただし、サブクラスのコンストラクター内で基本クラスのコンストラクターを呼び出すか、定義した仮想メソッド(Initialize()など)を呼び出すことで、コードを再利用できます。
C ++仮想コンストラクターイディオム。私の知る限り、C#は共変の戻り値型をサポートしていません。それは多くの人々のウィッシュリストに載っていると思います。
コンストラクタが多すぎることは、設計が壊れていることを示しています。コンストラクターが少なく、プロパティを設定する機能を備えたクラスを改善します。プロパティを本当に制御する必要がある場合は、同じ名前空間のファクトリを検討し、プロパティセッターを内部にします。ファクトリーにクラスのインスタンス化とそのプロパティの設定方法を決定させます。ファクトリーは、オブジェクトを適切に構成するために必要な数のパラメーターを取るメソッドを持つことができます。