web-dev-qa-db-ja.com

C#-パブリックに継承されたメソッドを非表示にできます(たとえば、派生クラスに対してプライベートにします)

パブリックメソッドAとBを持つBaseClassがあり、継承を通じてDerivedClassを作成するとします。

例えば.

public DerivedClass : BaseClass {}

次に、AとBを使用するDerivedClassでメソッドCを開発します。メソッドAとBをDerivedClassでプライベートにして、メソッドCのみをDerivedClassを使用したい人に公開する方法はありますか?

55
Lyndon

不可能です、なぜ?

C#では、パブリックメソッドを継承する場合、それらをパブリックにする必要があります。そうでなければ、彼らはあなたがそもそもクラスから派生しないことを期待します。

Is-a関係を使用する代わりに、has-a関係を使用する必要があります。

言語設計者は、これを意図的に許可しないため、継承をより適切に使用できます。

たとえば、クラスCarを誤ってクラスEngineから派生させて、その機能を取得する場合があります。しかし、エンジンは車で使用される機能です。したがって、has-a関係を使用する必要があります。車のユーザーは、エンジンのインターフェースにアクセスしたくありません。そして、車自体は、エンジンのメソッドとエンジンのメソッドを混同しないでください。ノーカーの将来の派生。

そのため、悪い継承階層からあなたを保護することはできません。

代わりに何をすべきですか?

代わりに、インターフェイスを実装する必要があります。これにより、has-a関係を使用した機能を自由に使用できます。

他の言語:

C++では、private、public、またはprotectedの基本クラスの前に修飾子を指定するだけです。これにより、指定されたアクセスレベルに対してパブリックであったベースのすべてのメンバーが作成されます。私には、C#で同じことを実行できないことは愚かに思えます。

再構成されたコード:

interface I
{
    void C();
}

class BaseClass
{
    public void A() { MessageBox.Show("A"); }
    public void B() { MessageBox.Show("B"); }
}

class Derived : I
{
    public void C()
    {
        b.A();
        b.B();
    }

    private BaseClass b;
}

上記のクラスの名前は少し議論の余地があることを理解しています:)

その他の提案:

他の人はA()およびB() publicにして例外をスローすることを提案しました。本当に意味がありません。

69
Brian R. Bondy

たとえば、_List<object>_から継承しようとして、直接Add(object _ob)メンバーを非表示にする場合:

_// the only way to hide
[Obsolete("This is not supported in this class.", true)]
public new void Add(object _ob)
{
    throw NotImplementedException("Don't use!!");
}
_

それは本当に最も望ましい解決策ではありませんが、それは仕事をします。 Intellisenseは引き続き受け入れますが、コンパイル時にエラーが発生します。

エラーCS0619: 'TestConsole.TestClass.Add(TestConsole.TestObject)'は廃止されました: 'これはこのクラスではサポートされていません。'

26
nono

それは悪い考えのように聞こえます。 Liskov は感動しません。

DerivedClassのコンシューマーがメソッドDeriveClass.A()およびDerivedClass.B()にアクセスできるようにしたくない場合、DerivedClassがパブリックインターフェイスIWhateverMethodCIsAboutを実装し、DerivedClassのコンシューマーが実際にIWhateverMethodCIsAboutと通信する必要があることをお勧めしますBaseClassまたはDerivedClassの実装については何もありません。

6
Hamish Smith

必要なのは、継承ではなく構成です。

class Plane
{
  public Fly() { .. }
  public string GetPilot() {...}
}

これで、PairOfWings = 2を持つような特別な種類のプレーンが必要な場合、それ以外はプレーンでできることはすべて実行されます。プレーンを継承します。これにより、派生が基本クラスのコントラクトを満たし、基本クラスが予想される場所であれば点滅せずに置換できることを宣言します。例えばLogFlight(Plane)は、BiPlaneインスタンスで引き続き動作します。

ただし、作成する新しいBirdのFly動作のみが必要で、完全な基本クラスコントラクトをサポートしたくない場合は、代わりに作成します。この場合、メソッドの動作をリファクタリングして新しいタイプのFlightに再利用します。 PlaneとBirdの両方でこのクラスへの参照を作成して保持します。 Birdは完全な基本クラスコントラクトをサポートしていないため、継承しません...(たとえば、GetPilot()を提供できません)。

同じ理由で、オーバーライド時にベースクラスメソッドの可視性を下げることはできません。派生でベースプライベートメソッドをオーバーライドおよびパブリックにできますが、その逆はできません。例えばこの例では、プレーンのタイプ「BadPlane」を派生し、オーバーライドして「非表示」GetPilot()-プライベートにします。クライアントメソッドLogFlight(Plane p)はほとんどのPlanesで機能しますが、LogFlightの実装がGetPilot()を必要とする/呼び出す場合は、「BadPlane」で爆発します。 基本クラスのすべての派生は、基本クラスのパラメーターが予想される場合はいつでも「置換可能」であると予想されるため、これは許可されない必要があります。

5
Gishu

私が知っているこれを行う唯一の方法は、Has-A関係を使用し、公開したい関数のみを実装することです。

3
Nescio

@Brian R. Bondyは、継承とnewキーワードによる隠蔽に関する興味深い記事を私に指摘しました。

http://msdn.Microsoft.com/en-us/library/aa691135(VS.71).aspx

回避策として、私はお勧めします:

class BaseClass
{
    public void A()
    {
        Console.WriteLine("BaseClass.A");
    }

    public void B()
    {
        Console.WriteLine("BaseClass.B");
    }
}

class DerivedClass : BaseClass
{
    new public void A()
    {
        throw new NotSupportedException();
    }

    new public void B()
    {
        throw new NotSupportedException();
    }

    public void C()
    {
        base.A();
        base.B();
    }
}

このように、このようなコードはNotSupportedExceptionをスローします:

    DerivedClass d = new DerivedClass();
    d.A();
3
Jorge Ferreira

これを実行したいコードベースがある場合、それは最適に設計されたコードベースではありません。通常、特定のパブリック署名を必要とする階層のあるレベルのクラスのサインですが、そのクラスから派生した別のクラスはそれを必要としません。

今後のコーディングパラダイムは、「継承を超える構成」と呼ばれます。これは、オブジェクト指向開発の原則(特に、単一責任原則およびオープン/クローズ原則)から直接外れています。

残念なことに、私たちの多くの開発者がオブジェクト指向を教えられた方法で、私たちはすぐに構成ではなく継承について考える習慣を形成しました。同じ「現実の世界」オブジェクトに含まれている可能性があるため、多くの異なる責任を持つより大きなクラスを持つ傾向があります。これにより、5レベル以上の深さのクラス階層が得られます。

開発者が継承を扱うときに通常考えない不幸な副作用は、継承がコードに導入できる最も強力な依存関係の1つを形成することです。派生クラスは、継承元のクラスに強く依存しています。これにより、長期的にはコードがより脆弱になり、ベースクラスの特定の動作を変更すると、派生クラスが不明瞭に壊れてしまうという混乱した問題につながります。

コードを分割する1つの方法は、別の回答で述べたようなインターフェースを使用することです。これは、クラスの外部依存関係を具象型/派生型ではなく抽象化にバインドするため、とにかく行うのが賢明です。これにより、依存クラスのコード行に影響を与えることなく、インターフェイスを変更せずに実装を変更できます。

ポリモーフィズム/継承を多用し、より緊密に結合されたクラスが少ないシステムを扱うよりも、すべてが小さく疎結合である数百/数千/さらに多くのクラスを備えたシステムを維持するよりもはるかに望ましいでしょう。

おそらく、オブジェクト指向開発に関するbestリソースは、ロバートC.マーティンの著書 アジャイルソフトウェア開発、原則、パターン、および実践)です。

1
Jason Olson

非表示はかなり滑りやすい斜面です。主な問題は、IMOです。

  • インスタンスのデザイン時宣言タイプに依存します。つまり、BaseClass obj = new SubClass()のようなことをしてからobj.A()を呼び出すと、非表示が無効になります。 BaseClass.A()が実行されます。

  • 非表示は、基本型の動作(または動作の変更)を非常に簡単に覆い隠す可能性があります。方程式の両側を所有している場合、または「base.xxx」の呼び出しがサブメンバーの一部である場合、これは明らかに懸念事項ではありません。

  • 実際にdoベース/サブクラス方程式の両側を所有している場合、制度化された非表示/シャドウイングよりも管理しやすいソリューションを考案できるはずです。
1
Jared

質問に対する答えは「いいえ」ですが、ここに到着する他の人に指摘したいヒントが1つあります(OPがサードパーティによるアセンブリへのアクセスを暗示するようなものだと仮定した場合)。他のユーザーがアセンブリを参照する場合、Visual Studioは次の属性を尊重する必要があるため、インテリセンスでは表示されません(非表示ですが、呼び出すことができますので注意してください)。

_[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
_

他に選択肢がない場合は、ベース型メソッドを非表示にするメソッドでnewを使用し、=> throw new NotSupportedException();を返し、上記の属性と組み合わせることができるはずです。

別のトリックは、ベースが対応するインターフェース(_IList<T>_の場合は_List<T>_など)がある場合、可能であればベースクラスから継承しないことに依存します。インターフェイスを「明示的に」実装すると、これらのメソッドはクラスタイプのインテリセンスから隠されます。例えば:

_public class GoodForNothing: IDisposable
{
    void IDisposable.Dispose() { ... }
}
_

var obj = new GoodForNothing()の場合、Dispose()メソッドはobjで使用できません。ただし、objIDisposableに明示的に型キャストする人は誰でも利用できます。

さらに、ベース型を継承する代わりにラップしてから、いくつかのメソッドを非表示にすることもできます。

_public class MyList<T> : IList<T>
{
    List<T> _Items = new List<T>();
    public T this[int index] => _Items[index];
    public int Count => _Items.Count;
    public void Add(T item) => _Items.Add(item);
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    void ICollection<T>.Clear() => throw new InvalidOperationException("No you may not!"); // (hidden)
    /*...etc...*/
}
_
0
James Wilkins

元のクラスでパブリックに定義されている場合、派生クラスでプライベートに変更することはできません。ただし、パブリックメソッドに例外をスローさせ、独自のプライベート関数を実装することもできます。

編集:ホルヘフェレイラは正しいです。

0
Cody Brocious