プライベートフィールドがインスタンスではなく型に対してプライベートであるのはなぜですか?


153

C#(および他の多くの言語)では、同じタイプの他のインスタンスのプライベートフィールドにアクセスすることは完全に正当です。例えば:

public class Foo
{
    private bool aBool;

    public void DoBar(Foo anotherFoo)
    {
        if (anotherFoo.aBool) ...
    }
}

ようにC#の仕様(セクション3.5.1、3.5.2)プライベートフィールドへのアクセスのタイプではなく、インスタンス上で述べています。私はこれについて同僚と話し合っており、(同じインスタンスへのアクセスを制限するのではなく)このように機能する理由を考え出そうとしています。

考えられる最良の引数は、クラスがプライベートフィールドにアクセスして別のインスタンスとの等価性を判断する可能性がある等価性チェックです。他に理由はありますか?それとも、このように機能しなければならない、または何かが完全に不可能であることを絶対に意味するいくつかの黄金の理由?


18
代替案のメリットは何ですか?
Jon Skeet、2011

19
実世界をうまくモデル化していないのは事実です。結局のところ、私の車が残したガスの量を報告できるからといって、私の車がすべての車の残したガスの量を知ることができるはずではありません。だから、はい、理論的には「プライベートインスタンス」アクセスがあることがわかります。99%のケースで、別のインスタンスがその変数にアクセスすることを本当に望んでいるのではないかと心配しているので、私は本当にそれを使用しないと思います。
aquinas

2
@Jonいわゆる「ベネフィット」の立場は、クラスが別のインスタンスの内部状態を知っているべきではないということです-同じタイプであるという事実に関係なく。一言で言うメリットはありませんが、おそらくどちらかを選択するのにはかなりの理由があるでしょう。それが、私たちが理解しようとしていることです
RichK、

4
変数は、コンストラクターで初期化されたゲッター/セッタークロージャーのペアにすることができます。これは、初期化時と同じではない場合に失敗します...
Martin Sojka

8
Scalaはインスタンスプライベートメンバーを提供します -Java、C#、および他の多くの言語にはない他の多くのアクセスレベルとともに これは完全に正当な質問です。
Bruno Reis

回答:


73

このように機能する理由の1つは、アクセス修飾子がコンパイル時に機能するためです。そのため、特定のオブジェクトが現在のオブジェクトでもあるかどうかを判断することは簡単ではありません。たとえば、次のコードを考えてみます。

public class Foo
{
    private int bar;

    public void Baz(Foo other)
    {
        other.bar = 2;
    }

    public void Boo()
    {
        Baz(this);
    }
}

コンパイラotherが実際にそれを理解できるのthisでしょうか?すべての場合ではありません。これでコンパイルできないはずだと主張する人もいますが、これは、正しいインスタンスのプライベートインスタンスメンバーアクセスできないコードパスがあることを意味します

唯一の問題は扱いやすいだけでなく、それはのように思える状況作りであることをタイプレベルではなく、オブジェクトレベルの可視性を必要性を保証すべき仕事、実際に仕事を。

編集:この推論が逆であるというダニレル・ヒルガースのポイントにはメリットがあります。言語デザイナーは必要な言語を作成でき、コンパイラー作成者はそれに準拠する必要があります。そうは言っても、言語設計者は、コンパイラー作成者が自分の仕事を簡単に行えるようにするインセンティブを持っています。(この場合でも、プライベートメンバーは(暗黙的または明示的に)経由でのみアクセスできると主張するのは簡単thisです)。

しかし、それが問題を必要以上に混乱させていると私は信じています。ほとんどのユーザー(私も含む)は、上記のコードが機能しない場合、不必要に制限していることに気づくでしょう。結局のところ、これがアクセスしようとしている私のデータです。なぜ私は通らなければならないのthisですか?

要するに、コンパイラーにとって「難しい」ということは、私は誇張しすぎたのではないかと思います。私が実際に乗り越えるつもりだったのは、上記の状況はデザイナーが仕事をしたいもののように思われるということです。


33
私見、この推論は間違った方法です。コンパイラーは言語の規則を実施します。それらを作成しません。言い換えると、プライベートメンバーが「インスタンスプライベート」であり、「タイププライベート」ではthis.bar = 2ない場合、別のインスタンスother.bar = 2であるother可能性があるため、コンパイラーは他の方法で実装されます。
Daniel Hilgarth、2011

@Danielそれは良い点ですが、私が述べたように、その制限があることはクラスレベルの可視性を持つことより悪いと思います。
dlev、2011

3
@dlev:「インスタンスプライベート」のメンバーが良いことだと思うかどうかについては何も言わなかった。:-)指摘したいのは、技術的な実装が言語の規則を規定しているとあなたが主張していることと、私の意見では、この議論は正しくないということです。
Daniel Hilgarth、2011

2
@Danielポイントが取得されました。私はまだこれは妥当な考慮事項だと思いますが、もちろん言語デザイナーが最終的に何を求めているかを理解し、コンパイラー作成者がそれに準拠することは正しいです。
dlev、2011

2
コンパイラの引数は成り立たないと思います。(Rubyのように)インスタンスプライベートのみを許可し、(エッフェルのように)インスタンスプライベートとクラスプライベートを選択可能にする言語が周りにあります。これらの言語では、コンパイラの生成はそれほど難しくも簡単でもありませんでした。詳細については、スレッドで私の答えも参照してください。
アベル

61

C#および類似の言語で使用される種類のカプセル化の目的はメモリ内の異なるオブジェクトではなく、コードの異なる部分(C#およびJavaのクラス)の相互依存性を低くすることです。

たとえば、別のクラスのいくつかのフィールドを使用する1つのクラスでコードを記述する場合、これらのクラスは非常に密に結合されています。ただし、同じクラスの2つのオブジェクトがあるコードを処理している場合、追加の依存関係はありません。クラスは常に自分自身に依存しています。

ただし、カプセル化に関するこのすべての理論は、誰かがプロパティ(またはJavaのget / setペア)を作成し、すべてのフィールドを直接公開するとすぐに失敗し、クラスがフィールドにアクセスしているかのように結合されます。

*カプセル化の種類については、Abelの優れた回答を参照してください。


3
get/setメソッドが提供されれば失敗するとは思いません。アクセサーメソッドは引き続き抽象化を維持します(ユーザーはその背後にフィールドがあること、またはプロパティの背後にある可能性のあるフィールドを知る必要はありません)。たとえば、Complexクラスを作成している場合は、極座標を取得/設定するプロパティと、デカルト座標を取得/設定するプロパティを公開できます。クラスがどのクラスを使用するかはユーザーにはわかりません(まったく異なる場合もあります)。
ケンウェインヴァンダーリンデ2011

5
@ケン:公開する必要があるかどうかさえ考慮せずに、所有するすべてのフィールドにプロパティを提供すると失敗します。
Goran Jovic

@Goran理想的ではないことに同意しますが、たとえば、基礎となるフィールドが変更された場合にget / setアクセサーを使用して追加のロジックを追加できるため、完全に失敗することはありません。
ForbesLindesay 2011

1
「カプセル化の目的は、さまざまなコードの相互依存性を低くすることだから」 >>いいえ。カプセル化はインスタンスのカプセル化を意味することもあり、それは言語や学問に依存しています。OOで最も決定的な声の1つであるBertrand Meyerは、これをのデフォルトでさえ考えていますprivate。よろしければ、私の答えについて私の意見を確認してください。
2011

@Abel:OPがそれらの1つについて尋ねたので、そして私がそれらを知っているので率直に言って、私はクラスカプセル化を使用する言語に私の回答を基づいています。修正と新しい興味深い情報をありがとう!
ゴランジョヴィッチ2011

49

この興味深いスレッドにはかなりの数の回答が既に追加されていますが、この振る舞いが現状のままである理由については、本当の理由はわかりませんでした。試してみましょう:

過去に戻って

80年代のSmalltalkと90年代半ばのJavaの間のどこかで、オブジェクト指向の概念が成熟しました。クラスのすべてのデータ(フィールド)はプライベートであり、すべてのメソッドはパブリックであるため、もともとOOだけが利用できる概念(1978年に最初に言及)とは考えられていなかった情報の非表示は、Smalltalkで導入されました。90年代のOOの多くの新しい開発の間に、ベルトランドマイヤーは、彼の画期的な本であるオブジェクト指向ソフトウェア構築(OOSC)でOOの概念の多くを形式化しようとしました。 。

プライベートビジビリティの場合

Meyer氏によると、定義されたクラスのセット(192〜193ページ)でメソッドを使用できるようにする必要があります。これにより、非常に詳細な情報の非表示が明らかになります。次の機能は、classAとclassBおよびそのすべての子孫で使用できます。

feature {classA, classB}
   methodName

彼の場合private、次のように述べています。型をそれ自体のクラスに可視であると明示的に宣言しないと、修飾された呼び出しでその機能(メソッド/フィールド)にアクセスできません。つまりx、が変数の場合、x.doSomething()許可されません。もちろん、クラス自体の内部で無資格のアクセスが許可されます。

つまり、同じクラスのインスタンスによるアクセスを許可するには、そのクラスによるメソッドアクセスを明示的に許可する必要があります。これは、instance-privateとclass-privateと呼ばれることもあります。

プログラミング言語でのインスタンスプライベート

クラスプライベートの情報非表示とは対照的に、インスタンスプライベートの情報非表示を使用する、現在使用中の少なくとも2つの言語を知っています。1つは、メイヤーが設計した言語であるエッフェルであり、OOを極限まで引き上げます。もう1つはRubyで、現在でははるかに一般的な言語です。Rubyでは、「このインスタンスにプライベート」をprivate意味します

言語設計の選択肢

instance-privateを許可することはコンパイラーにとって困難であることが示唆されています。メソッドへの修飾された呼び出しを許可または禁止することは比較的簡単なので、私はそうは思いません。プライベートメソッドdoSomething()が許可されている場合と許可されてx.doSomething()いない場合、言語デザイナーはプライベートメソッドとフィールドに対してインスタンスのみのアクセシビリティを効果的に定義しています。

技術的な観点からは、どちらかを選択する理由はありません(特に、Eiffel.NETがILでこれを行うことができると考えると、多重継承があっても、この機能を提供しない固有の理由はありません)。

もちろん、これは好みの問題であり、すでに述べたように、一部のメソッドは、プライベートメソッドとフィールドのクラスレベルの可視性の機能がないと作成が難しい場合があります。

C#がクラスのカプセル化のみを許可し、インスタンスのカプセル化を許可しない理由

インスタンスのカプセル化に関するインターネットスレッド(言語がクラスレベルではなく、インスタンスレベルでアクセス修飾子を定義することを指す用語として使用される場合があります)を見ると、この概念はよく見過ごされます。ただし、一部の最近の言語では、少なくともプライベートアクセス修飾子に対してインスタンスのカプセル化を使用していることを考えると、それは、現在のプログラミングの世界で可能であり、実用的であると考えるようになります。

ただし、C#はその言語設計についてC ++とJavaを最も厳しく見ていました。EiffelとModula-3も画像に含まれていますが、Eiffelの多くの機能がないこと(多重継承)を考慮すると、プライベートアクセス修飾子に関しては、JavaやC ++と同じルートを選択したと思います。

Eric Lippert、Krzysztof Cwalina、Anders Hejlsberg、またはC#の標準に取り組んでいる他の誰かを手に入れようとする理由を本当に知りたい場合は、残念ながら、注釈付きのC#プログラミング言語に明確な注釈はありませんでした。


1
これは背景がたくさんある素敵な回答です。非常に興味深いです。(比較的)古い質問に答えて
くれてありがとう

1
@RichK:どういたしまして、質問に間に合わなかったと思いますが、このトピックは、深みをもって対応するのに十分興味をそそられました:)
Abel

COMでは、「プライベート」は「インスタンスプライベート」を意味しました。クラスの定義がFooインターフェースと実装クラスを効果的に表現しているため、これはある程度測定されたと思います。COMにはクラスオブジェクト参照(インターフェイス参照のみ)の概念がなかったため、クラスがそのクラスの別のインスタンスであることが保証されているものへの参照を保持する方法はありませんでした。このインターフェイスベースの設計は、いくつかのことを扱いにくくしましたが、同じ内部を共有する必要なく、別のクラスに置き換えることができるクラスを設計できることを意味しました。
supercat

2
すばらしい答えです。OPは、この質問に回答をマークすることを検討する必要があります。
Gavin Osborn 2013年

18

これは私の意見にすぎませんが、実用的には、プログラマーがクラスのソースにアクセスできる場合、クラスインスタンスのプライベートメンバーにアクセスすることで合理的に信頼できると思います。プログラマが彼らの左側にすでに王国への鍵を与えているのに、なぜプログラマを右手を縛るのか?


13
私はこの議論を理解していません。あなたが自分が唯一の開発者であり、自分がライブラリを使用する唯一の人であるプログラムに取り組んでいるとしましょう。その場合、ソースコードにアクセスできるため、すべてのメンバーを公開するとしますか?
aquinas

@aquinas:それはかなり違います。すべてを公開すると、恐ろしいプログラミング慣行につながります。型が他のインスタンスのプライベートフィールドを参照できるようにすることは、非常に狭いケースです。
Igby Largeman、2011

2
いいえ、すべてのメンバーを公開することを推奨しているわけではありません。天国はありません!私の主張は、もしあなたがすでにソースにいるなら、プライベートメンバー、それらがどのように使用されるか、採用されている慣習などを見ることができるのであれば、なぜその知識を使うことができないのでしょうか?
FishBasketGordo

7
考え方は今のところ型だと思います。型のメンバーを適切に処理するために型を信頼できない場合、何ができますか?(通常、それは実際に対話を制御するプログラマーですが、コード生成ツールのこの時代では、常にそうであるとは限りません。)
Anthony Pegram

3
私の質問は本当に信頼の問題ではなく、必要性の問題です。リフレクションを使用してプライベートに凶悪なことをすることができる場合、信頼はまったく関係ありません。概念上、なぜプライベートにアクセスできるのかと思います。ベストプラクティスの状況ではなく、論理的な理由があると思いました。
RichK、2011

13

実際の理由は、等価性チェック、比較、複製、演算子のオーバーロードです...たとえば、複素数にoperator +を実装するのは非常に難しいでしょう。


3
しかし、あなたが言及するすべてのものは、1つのタイプが別のタイプの値を取得することを必要とします。私は言っています:私の状態を調べたいですか?よし、どうぞ。私のプライベートな状態を設定しますか?まさか、相棒、あなた自身のいまいましい状態を変えてください。:)
aquinas

2
@aquinasそのようには動作しません。フィールドはフィールドです。これらは、宣言されている場合にのみ読み取り専用でありreadonly、宣言インスタンスにも適用されます。明らかに、これを禁止する特定のコンパイラルールが必要であると主張していますが、そのようなルールはすべて、C#チームが仕様化、文書化、ローカライズ、テスト、保守、およびサポートする必要がある機能です。説得力のある利点がない場合、なぜ彼らはそれをするのでしょうか?
アーロノート

@Aaronaughtに同意します。すべての新しい仕様の実装とコンパイラの変更に関連するコストがあります。セットシナリオでは、クローンメソッドを検討してください。確かに、プライベートコンストラクターで実現したい場合もありますが、場合によっては不可能/推奨されません。
ロレンツォデマッテ

1
C#チーム?私はすべての言語で起こり得る理論的な言語の変化について議論しているだけです。「説得力のあるメリットがない場合...」メリットがあるかもしれません。ちょうどと同じように任意のコンパイラの保証、誰かがそれは有用なクラスだけで、自身の状態を変更することを保証するかもしれません。
aquinas

@aquinas:機能多くのユーザーまたはほとんどのユーザーに役立つために実装されます。一部の孤立したケースで役立つ可能性があるためではありません
アーロンノート、2011

9

まず最初に、プライベート静的メンバーはどうなりますか?静的メソッドでのみアクセスできますか?あなたは確かにそれを望んでいないでしょう。それはあなたが自分のにアクセスすることができないからですconst

あなたの明確な質問に関して、StringBuilderそれ自身のインスタンスのリンクされたリストとして実装されるの場合を考えてください:

public class StringBuilder
{
    private string chunk;
    private StringBuilder nextChunk;
}

自分のクラスの他のインスタンスのプライベートメンバーにアクセスできない場合は、次のように実装する必要がありますToString

public override string ToString()
{
    return chunk + nextChunk.ToString();
}

これは機能しますが、O(n ^ 2)です-あまり効率的ではありません。実際、それはおそらくStringBuilder、そもそもクラスを持つという目的全体を打ち負かすことでしょう。自分のクラスの他のインスタンスのプライベートメンバーにアクセスできる場合ToStringは、適切な長さの文字列を作成し、文字列内の適切な場所に各チャンクの安全でないコピーを行うことで実装できます。

public override string ToString()
{
    string ret = string.FastAllocateString(Length);
    StringBuilder next = this;

    unsafe
    {
        fixed (char *dest = ret)
            while (next != null)
            {
                fixed (char *src = next.chunk)
                    string.wstrcpy(dest, src, next.chunk.Length);
                next = next.nextChunk;
            }
    }
    return ret;
}

この実装はO(n)であり、非常に高速になり、クラスの他のインスタンスのプライベートメンバーにアクセスできる場合にのみ可能です


そうですが、いくつかのフィールドを内部として公開するなど、他の方法はありませんか?
MikeKulls、2011

2
@マイク:としてフィールドを公開internal作ることはあまりプライベート!クラスの内部をそのアセンブリ内のすべてに公開することになります。
Gabe

3

これは多くの言語で完全に合法です(1つはC ++)。アクセス修飾子は、OOPのカプセル化の原則に基づいています。外部へのアクセスを制限するという考え方です。この場合、外部は他のクラスです。たとえば、C#のネストされたクラスは、その親のプライベートメンバーにもアクセスできます。

これは言語デザイナーの設計上の選択ですが。このアクセスの制限は、エンティティの分離に大きく貢献することなく、いくつかの非常に一般的なシナリオを複雑にする可能性があります。

ここで同様の議論があります


1

データが各インスタンスにプライベートであるという別のレベルのプライバシーを追加できなかった理由はないと思います。実際、それは言語に完全性の良い感じを与えるかもしれません。

しかし、実際には、それが本当に役立つとは思えません。ご指摘のとおり、通常のプライベート性は、等価性チェックや、Typeの複数のインスタンスに関連する他のほとんどの操作などに役立ちます。ただし、データの抽象化を維持することについては、OOPの重要なポイントであるため、その点も気に入っています。

このようにアクセスを制限する機能を提供することは、OOPに追加する優れた機能になると思います。本当に便利なのでしょうか?クラスはそれ自体のコードを信頼できるはずなので、私はノーと言います。そのクラスはプライベートメンバーにアクセスできる唯一のものであるため、別のクラスのインスタンスを処理するときにデータの抽象化が必要になる本当の理由はありません。

もちろん、インスタンスにプライベートを適用する場合と同じように、いつでもコード記述できます。通常のget/set方法でデータにアクセス/変更します。これにより、クラスが内部で変更される可能性がある場合に、コードが管理しやすくなります。


0

上記の素晴らしい答え。この問題の一部は、クラス自体の内部でのインスタンス化がそもそも許可されているという事実であると付け加えます。たとえば、再帰ロジックの "for"ループでは、再帰を終了するロジックがある限り、そのタイプのトリックを使用するためです。しかし、そのようなループを作成せずに同じクラスをインスタンス化または内部に渡すと、論理的には広く受け入れられているプログラミングパラダイムであるにもかかわらず、独自の危険が生じます。たとえば、C#クラスは、デフォルトのコンストラクターでそれ自体のコピーをインスタンス化できますが、それによって規則が破られたり、因果ループが作成されたりすることはありません。どうして?

ところで...これと同じ問題は「保護された」メンバーにも当てはまります。:(

ほとんどのプログラマーがこの問題のような問題が発生して人々を混乱させ、プライベートメンバーを持つ理由全体を無視するまで完全に把握できない一連の問題とリスクがまだあるため、そのプログラミングパラダイムを完全に受け入れたことはありません。

C#のこの「奇妙で奇抜な」側面は、優れたプログラミングが経験やスキルとは何の関係もないが、トリックやトラップを知っているだけで、自動車での作業と同じように、もう1つの理由です。ルールが破られることを意図していたという主張は、どの計算言語にとっても非常に悪いモデルです。


-1

データが同じタイプの他のインスタンスにプライベートである場合、それは必ずしも同じタイプであるとは思えないようです。他のインスタンスと同じように動作または動作しないようです。そのプライベートな内部データに基づいて、動作を簡単に変更できます。それは私の意見に混乱を広げるだけです。

大まかに言って、私は個人的に、基本クラスから派生したクラスを作成することで、「インスタンスごとにプライベートデータを保持する」と同じような機能を提供できると思います。代わりに、「一意の」タイプごとに新しいクラス定義があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.