web-dev-qa-db-ja.com

プライベート仮想メソッドがC#で違法なのはなぜですか?

C++のバックグラウンドから来て、これは私にとって驚きでした。 C++では、仮想関数をプライベートにすることをお勧めします。から http://www.gotw.ca/publications/mill18.htm : "ガイドライン#2:仮想関数をプライベートにすることをお勧めします。"

Knights-knaves-protected-and-internal のEric Lippertのブログも引用しています。

プライベート仮想メソッドはC#では違法です。もしあれば、私はその機能を完全に使用します。

C#では、派生(ネストされていない)クラスのプライベート仮想メソッドをオーバーライドできないことを理解しています。これはなぜですか? C++では、アクセス指定子は、関数をオーバーライドできるかどうかとは関係ありません。

40
Jon

ここで2つの質問があることに注意してください。将来的には、2つの質問を1つにまとめるのではなく、2つの質問を投稿することを検討するかもしれません。このような質問を組み合わせると、最初の質問だけが回答されます。

最初の質問は、「なぜC#でプライベート仮想メソッドが違法なのか」です。

以下は、「プライベート仮想メソッド」機能に対する反対意見です。

  1. プライベート仮想は、ネストされた派生クラスがある場合にのみ役立ちます。これは有用なパターンですが、ネストされていない派生クラスの状況ほど一般的ではありません。

  2. ネストされていない派生クラスのメソッドをoverrideメソッドに制限したい場合は、ネストされていないクラスが基本クラスから派生する機能を制限することで可能です。すべての基本クラスコンストラクターをプライベートにします。したがって、オーバーライドを防ぐためにプライベート仮想は必要ありません。唯一の派生クラスがネストされるため、保護された仮想で十分です。

  3. ネストされていない派生クラスのメソッドをcallメソッドに制限する場合は、メソッドを内部仮想化し、同僚にそのメソッドを使用しないように指示できます。これがコンパイラーによって強制されないのはイライラしますが、コンパイラーはメソッドの使用方法に関する他の意味上の制約も強制しません。セマンティクスを正しくするのはあなたのビジネスであり、コンパイラーのビジネスではありません。適切なコードレビューでそれを強制する必要があります。したがって、呼び出しを防ぐためにプライベート仮想は必要ありません。内部仮想プラスコードレビューで十分です。

  4. このパターンを既存のパーツですでに実装することは可能です:

    abstract class C
    {
        private int CF() { whatever; }
        private Func<int> f;
        public C() { f = CF; } 
        private int F() { return f(); }
        private class D : C
        {
            private int DF() { whatever; }
            public D() { f = DF; }
        }
    

    これで、メソッドFが実質的に仮想ですが、ネストされた派生クラスによってのみ「オーバーライド」できます。

いずれの場合も、保護された内部、保護された内部のいずれかで問題が解決されるため、プライベート仮想は不要です。ネストされた派生クラスパターンを使用することに既にコミットしている必要があるため、これを行うことはほとんど正しいことではありません。したがって、その言語はそれを違法にします。

の引数は次のとおりです。

実際のコードでは、ネストされていない内部クラスとネストされた内部クラスの両方で拡張したいクラスのプライベート実装の詳細を仮想メソッドにしたい場合があります。同僚が内部メソッドを呼び出せないという不変条件を適用する必要があることは、気の毒なことです。デリゲート型のフィールドを作成するなどのクレイジーなフープを飛ばさなくても、コンパイラーによって強制されるようにしたいと思います。

また、単に一貫性と直交性の問題があります。独立しているべき2つのもの、つまりアクセシビリティと仮想性が不必要に互いに影響し合うのは奇妙に思えます。

この機能に対する議論はかなり強力です。の議論はかなり弱いです。したがって、そのような機能はありません。個人的にはとても気に入っていますが、デザインチームが私を取り上げなかった理由を完全に理解しています。それはコストに見合う価値がありませんし、私はnotより良い機能を出荷したくありません。

2番目の質問は、「C#では、ネストされていない派生クラスでプライベート仮想メソッドをオーバーライドできないのはなぜですか?」

いくつかの理由があります。

  1. あなたが見ることができるものだけを上書きできるからです。プライベートメソッドは、基本クラスのプライベート実装の詳細であり、アクセス可能であってはなりません。

  2. それを許可すると、深刻なセキュリティ上の影響があるためです。 C++では、ほとんどの場合、コードを一度にすべてアプリケーションにコンパイルします。すべてのソースコードがあります。ほとんどの場合、C++の観点から見ると、すべてが本質的に「内部」です。 C#では、そうではありません。サードパーティのアセンブリは、ライブラリからパブリックタイプを簡単に取得し、それらのクラスへの新しい拡張を生成して、基本クラスのインスタンスの代わりにシームレスに使用できます。仮想メソッドはクラスの動作を効果的に変更するため、セキュリティ上の理由からそのクラスの不変式に依存するコードは、基本クラスによって保証される不変式に依存しないように注意深く設計する必要があります。仮想メソッドのアクセシビリティを制限すると、それらのメソッドの不変条件が確実に維持されます。

  3. それを可能にすることは、もろい基本クラス問題の別の形を提供するからです。 C#は、他の言語よりも脆弱な基本クラスの問題の影響を受けにくいように注意深く設計されていますOO言語。アクセスできない仮想メソッドが派生クラスでオーバーライドされる可能性がある場合、基本クラスのプライベート実装の詳細変更された場合、重大な変更になります。基本クラスのプロバイダーは、それらに依存する派生クラスを壊したことを心配することなく、内部の詳細を自由に変更できる必要があります。詳細変更。

38
Eric Lippert

プライベートメソッドは、それらを定義するクラスからのみアクセスできるため、プライベート仮想メソッドは役に立たないでしょう。必要なのは、保護された仮想メソッドです。保護されたメソッドには、それを定義するクラスとサブクラスからアクセスできます。

編集:

プライベートおよび保護されたキーワードを提供することにより、C#ではメソッドをより詳細に制御できます。つまり、プライベートは完全に閉じられ、保護はサブクラスを除いて完全に閉じられることを意味します。これにより、スーパークラスだけが知っているメソッドと、サブクラスが知っているメソッドを持つことができます。

15
Darko Z

その理由は、internal virtualほぼprivate virtualと同じであり、private virtualイディオムに慣れていない人にとっては混乱が少ないためだと思います。

内部クラスのみがprivate virtualメソッドをオーバーライドできましたが、アセンブリ内のクラスのみがinternal virtualメソッドをオーバーライドできます。

4
Dean Harding

C#(およびCLIでは、これまで見てきた限り)では、「プライベート」は「このクラスでのみアクセス可能」というかなり明確で明確な意味があります。名前空間を地雷原のようにすることは言うまでもありませんが、プライベートバーチャルの概念はそれを覆します。なぜあなたは私が見ることさえできないメソッドと呼んでいるものを気にしなければならず、そしてあなたがすでにそれを手に入れている名前を偶然に選んだことについてコンパイラ警告を得なければならないのですか?

2
cHao

C#には、パブリック/プライベート/保護された継承を提供するメカニズムがないためです。

C++でも、プライベートメンバーは派生クラスからアクセスできませんが、継承の可視性を指定することにより、基本クラスの可視性を制限できます。

class Derived : /*->>*/private/*<--*/ Base {
}

C#には、クラスのメンバーの可視性を制御するために、他にもたくさんのものが用意されています。 protectedinternalの間では、希望どおりに階層を取得できます。

IMHO C#は、単一の基本クラス継承を介してより強力なIS-A関係を実施するため、車にエンジンがある場合、BMWサブクラスはそれを隠すことができないはずです。

C++は、IS-A関係の厳密性が低い多重継承をサポートしています。これは、無関係な複数のクラスを取り込むことができるHAS-A関係とほぼ同じです。複数の基本クラスを取り込むことができるため、それらすべての可視性をより厳密に制御する必要があります。

2
Igor Zevaka

これを明確にしましょう。C#はC++ではありません。

C#はC++から数十年後に設計され、長年にわたる進歩した洞察を使用して構築されています。私の控えめな意見では、C#は明確に定義されており、最終的にはオブジェクトの方向を正しく処理します(imho)。これには、理由によりinternalステートメントが含まれており、プライベートメソッドを「仮想化」してオーバーライドすることはできません。理由があります。

前に説明したすべての問題(private virtualメソッドをオーバーライドする内部クラス、このように抽象的なファクトリパターンを使用するなど)は、インターフェイスとinternalステートメントを使用して別の方法で簡単に記述できます。そうは言っても、C++の方法とC#の方法のどちらが好きかはかなり好みの問題だと言わざるを得ません。

私は説明的なコード(コメントを使用せずにそれ自体を表すコード)を使用することを好み、深い継承の代わりにインターフェースを使用します。プライベート仮想メソッドをオーバーライドすることは、ハッキングやスパゲッティのように感じられます。それが一般的な方法であるか、よく使用されるパターンであるか、または仕事を成し遂げるかは関係ありません。

私はほぼ15年間C++で開発してきましたが、プライベートメソッドをオーバーライドする必要に遭遇したことはありません...(コメントが飛んでいるのがわかります:-))

1
Rob Vermeulen