3

下位クラスでは、基本クラスのコンストラクターを呼び出しながら、パラメーター化されたパブリック コンストラクターと保護/プライベート コンストラクターの両方を呼び出す方法はありますか?

たとえば、次のコードがあるとします。

using System;

public class Test
{
  void Main()
  {
    var b = new B(1);
  }

  class A
  {
    protected A()
    {
      Console.WriteLine("A()");
    }


    public A(int val)
      : this()
    {
      Console.WriteLine("A({0})", val);
    }

  }

  class B : A
  {
    protected B()
    {
      Console.WriteLine("B()");
    }

    public B(int val)
      : base(val)
    {
      Console.WriteLine("B({0})", val);
    }

  }
}

与えられた出力は次のとおりです。

A()
A(1)
B(1)

しかし、これは私が期待していたものです:

A()
B()
A(1)
B(1)

コンストラクターチェーンを介してこれを達成する方法はありますか? または、でオーバーライドされ、保護されたパラメーターなしのコンストラクターから呼び出される抽象または仮想のOnInitialize()型メソッドが必要ですか?ABA

4

2 に答える 2

5

あなたのコメントには2つの誤解があります。

まず、派生クラスのコンストラクターは、基本クラスのコンストラクターをオーバーライドしません。むしろ、彼らはそれらを呼びます。メソッドをオーバーライドするということは、基本クラスの同等のメソッドの代わりに呼び出されることを意味します。

第二に、と呼ばれる基本コンストラクターが常にあります。基本コンストラクターを明示的に呼び出さないコンストラクターは、パラメーターなしの基本コンストラクターを暗黙的に呼び出します。

A()これは、コンストラクターを削除するか、プライベートにすることで実証できます。B()基本コンストラクターを呼び出さないコンストラクターは、暗黙の呼び出しに使用できるパラメーターなしのコンストラクターがないため、コンパイラーエラーになります。

Bから派生しているためAB ですA。したがってB、構築せずに正常に構築することはできませんA(車を構築せずにスポーツカーを構築する場合など)。

建設はベースファーストで行われます。AのインスタンスAが完全に構築され、有効な状態にあることを確認するのは、のコンストラクターの責任です。これは、BのコンストラクターがのインスタンスをB有効な状態にするための開始点です。

のアイデアについて:

または、抽象または仮想のOnInitialize()タイプのメソッドをAに含める必要があります。これは、Bでオーバーライドされ、Aの保護されたパラメーターのないコンストラクターから呼び出されますか?

絶対にありません。のコンストラクターが呼び出された時点でAは、残りBはまだ構築されていません。ただし、イニシャライザーは実行されています。これは、次のような本当に厄介な状況につながります。

public abstract class A
{
  private int _theVitalValue;
  public A()
  {
    _theVitalValue = TheValueDecider();
  }
  protected abstract int TheValueDecider();
  public int TheImportantValue
  {
    get { return _theVitalValue; }
  }
}
public class B : A
{
  private readonly int _theValueMemoiser;
  public B(int val)
  {
    _theValueMemoiser = val;
  }
  protected override int TheValueDecider()
  {
    return _theValueMemoiser;
  }
}
void Main()
{
  B b = new B(93);
  Console.WriteLine(b.TheImportantValue); // Writes "0"!
}

Aでオーバーライドされた仮想メソッドの呼び出し時にBBのイニシャライザーは実行されましたが、残りのコンストラクターは実行されていません。したがって、仮想メソッドはB、有効な状態にないで実行されています。

そのようなアプローチを機能させることは確かに可能ですが、それが多くの苦痛を引き起こすことも本当に簡単です。

その代わり:

クラスを定義して、それぞれが常にコンストラクターを完全に初期化された有効な状態のままにするようにします。この時点で、そのクラスの不変条件を設定します(不変条件とは、クラスが外部から観察されるときに常に当てはまる必要がある条件のセットです。たとえば、内部で月と日を保持する日付クラスでも問題ありません。別のフィールドの値は一時的に2月31日の状態になりますが、呼び出しコードは日付であると想定するため、構築の終了時、メソッドの開始時または終了時はこのような状態であってはなりません。 )。

抽象クラスがその役割を果たすために派生クラスに依存することは問題ありませんが、初期状態についてはそうではありません。設計がこれが必要になる方向に進んでいる場合は、その状態の責任をさらに後継者に移します。たとえば、次のことを考慮してください。

public abstract class User
{
  private bool _hasFullAccess;
  protected User()
  {
    _hasFullAccess = CanSeeOtherUsersItems && CanEdit;
  }
  public bool HasFullAccess
  {
    get { return _hasFullAccess; }
  }
  protected abstract bool CanSeeOtherUsersItems {get;}
  protected abstract bool CanEdit {get;}
}
public class Admin : User
{
  protected override bool CanSeeOtherUsersItems
  {
    get { return true; }
  }
  protected override bool CanEdit
  {
    get { return true; }
  }
}
public class Auditor : User
{
  protected override bool CanSeeOtherUsersItems
  {
    get { return true; }
  }
  protected override bool CanEdit
  {
    get { return false; }
  }
}

ここでは、派生クラスの情報に基づいて基本クラスの状態を設定し、その状態に基づいてタスクを実行します。これはここでは機能しますが、プロパティが派生クラスの状態に依存している場合は機能しません(新しい派生クラスを作成する人が許可されていると想定するのは完全に合理的です)。代わりに、次のように変更することで、機能を作成しますが、状態は派生クラスに依存しませんUser

public abstract class User
{
  public bool HasFullAccess
  {
    get { return CanSeeOtherUsersItems && CanEdit; }
  }
  protected abstract bool CanSeeOtherUsersItems {get;}
  protected abstract bool CanEdit {get;}
}

(このような呼び出しが高額になることがあると予想した場合でも、最初の呼び出しで結果をメモすることができます。これは、完全に構築されUserた状態でのみ発生する可能性があり、メモ化の前後の状態は有効で正確な状態であるためですUser。 )。

コンストラクターを有効な状態のままにする基本クラスを定義したら、派生クラスに対しても同じことを行います。基本クラスは完全に構築されており、それが構築のステップ1であるため、コンストラクター内の基本クラスの状態に依存する可能性があります。イニシャライザーではこれに依存することはできませんが、実際には、人々が試したいものほど近くにはありません。

期待される出力に戻るには:

A()
B()
A(1)
B(1)

これは、何かを有効な開始状態にしてから、別の有効な開始状態にしたかったことを意味します。それは意味がありません。

もちろん、フォームを使用しthis()てコンストラクターからコンストラクターを呼び出すこともできますが、これは純粋に入力を節約するための便宜と見なす必要があります。これは、「このコンストラクターは、呼び出されたコンストラクターが実行するすべてのことを実行し、次にいくつかを実行する」ことを意味します。外の世界では、1つのコンストラクターだけが呼び出されました。この便利さがない言語では、コードを複製するか、次のようにします。

private void repeatedSetupCode(int someVal, string someOtherVal)
{
  someMember = someVal;
  someOtherMember = someOtherVal;
}
public A(int someVal, string someOtherVal)
{
  repeatedSetupCode(someVal, someOtherVal);
}
public A(int someVal)
{
  repeatedSetupCode(someVal, null);
}
public A()
{
  repeatedSetupCode(0, null);
}

私たちが持っているのはいいことですthis()。タイピングの節約にはなりませんが、問題の要素がオブジェクトの存続期間のみの構築フェーズに属していることは明らかです。ただし、本当に必要な場合は、上記のフォームを使用して、repeatedSetupCode保護することもできます。

しかし、私は非常に用心深いでしょう。その基本クラスをその基本クラスの不変条件に従って有効な状態にするために必要な単一の基本コンストラクターを呼び出す以外の方法で互いの構築を処理するオブジェクトは、別のアプローチが完全に優れている可能性があることを示唆する悪臭です。

于 2012-01-25T01:13:40.240 に答える
1

いいえ、コンストラクターだけを使用して探しているものは不可能です。基本的に、コンストラクタチェーンに「分岐」を要求していますが、これは不可能です。各コンストラクターは、現在のクラスまたは親クラスのいずれかで1つ(そして1つだけ)のコンストラクターを呼び出すことができます。

これを実現する唯一の方法は、(あなたが提案するように)基本クラスで呼び出す仮想初期化関数を使用することです。通常、コンストラクター内から仮想メンバーを呼び出すことは、子クラスのコードをコンストラクターの前に実行させることができるため、嫌われますが、それがまさにあなたが求めているものであることを考えると、ここでの唯一の実際のアプローチです。

于 2012-01-25T00:07:36.437 に答える