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

StackOverflow https://stackoverflow.com/questions/106383

  •  01-07-2019
  •  | 
  •  

質問

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

例えば

public DerivedClass : BaseClass {}

ここで、A と B を使用する DerivedClass のメソッド C を開発したいと思います。DerivedClass でメソッド A と B をプライベートになるようにオーバーライドして、メソッド C のみが DerivedClass を使用したい人に公開されるようにする方法はありますか?

役に立ちましたか?

解決

それは不可能です、なぜですか?

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

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

言語設計者は、継承をより適切に使用できるように、これを意図的に許可していません。

たとえば、機能を取得するためにクラス Engine から派生したクラス Car を誤って混同する可能性があります。しかし、エンジンは車が使用する機能です。したがって、has-a 関係を使用するとよいでしょう。車のユーザーは、エンジンのインターフェースにアクセスすることを望んでいません。そして、車自体は、エンジンのメソッドとそれ自体のメソッドを混同すべきではありません。Car の将来の派生品でもありません。

そのため、不正な継承階層からユーザーを保護することは許可されていません。

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

代わりにインターフェイスを実装する必要があります。これにより、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() をパブリックにして例外をスローすることを提案する人もいます。しかし、これでは人々が使いやすいクラスとは言えず、実際には意味がありません。

他のヒント

たとえば、 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)」は廃止されました。「これはこのクラスではサポートされていません。」

それは悪い考えのように思えます。 リスコフ 感動しないだろう。

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

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

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

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

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

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

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

@ブライアンR.Bondy は、継承による隠蔽に関する興味深い記事を教えてくれました。 新しい キーワード。

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();
    }
}

このようにすると、このようなコードは サポートされていない例外:

    DerivedClass d = new DerivedClass();
    d.A();

隠れるのはかなり滑りやすい坂道です。IMOによる主な問題は次のとおりです。

  • インスタンスの設計時間宣言タイプに依存します。つまり、baseclass obj = new subclass()のようなことを行う場合、obj.a()を呼び出し、隠れが敗北します。BaseClass.A() が実行されます。

  • 隠すことは、ベースタイプの動作(または動作の変化)を非常に簡単に覆い隠すことができます。これは、方程式の両側を所有している場合、または「base.xxx」を呼び出すことがサブメンバーの一部である場合、明らかに懸念事項ではありません。

  • もしあなたが実際に する 基本/サブクラス方程式の両辺を所有していれば、制度化された隠蔽/シャドウイングよりも管理しやすい解決策を考案できるはずです。

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

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

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

開発者が継承を扱うときに通常考慮しない残念な副作用は、継承がコードに導入できる依存関係の最も強力な形式の 1 つを形成することです。派生クラスは、継承元のクラスに強く依存するようになりました。これにより、長期的にはコードがより脆弱になり、基本クラスの特定の動作を変更すると派生クラスがわかりにくい形で壊れるという、混乱を招く問題が発生する可能性があります。

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

私は、ポリモーフィズムや継承を多用し、より密に結合されたクラスが少ないシステムを扱うよりも、すべて小さく疎結合の数百、数千、さらにはそれ以上のクラスを含むシステムを維持したいと考えています。

おそらく、 最高 オブジェクト指向開発に関するリソースは Robert C.マーティンの本、 アジャイル ソフトウェア開発、原則、パターン、実践.

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

編集:ホルヘ・フェレイラの言うことは正しい。

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

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

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

もう 1 つのトリックは、可能であれば基本クラスから継承しないことに依存します。この場合、基本クラスには対応するインターフェイス (たとえば、 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...*/
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top