C#(および他の多くの言語)では、同じタイプの他のインスタンスのプライベートフィールドにアクセスすることは完全に合法です。例えば:
public class Foo
{
private bool aBool;
public void DoBar(Foo anotherFoo)
{
if (anotherFoo.aBool) ...
}
}
C#仕様 (セクション3.5.1、3.5.2)には、プライベートフィールドへのアクセスはインスタンスではなくタイプに対するものであると記載されています。私はこれを同僚と議論してきましたが、(同じインスタンスへのアクセスを制限するのではなく)このように機能する理由を考えています。
最良の引数は、クラスがプライベートフィールドにアクセスして別のインスタンスとの同等性を判断する同等性チェックです。他に理由はありますか?または、絶対にこのように動作する必要があることを意味する黄金の理由、または何かが完全に不可能になるでしょうか?
この方法で動作する理由の1つは、アクセス修飾子がコンパイル時間で動作するためだと思います。そのため、特定のオブジェクトがcurrentオブジェクトでもあるかどうかを判断するのは簡単ではありません。たとえば、次のコードを検討してください。
public class Foo
{
private int bar;
public void Baz(Foo other)
{
other.bar = 2;
}
public void Boo()
{
Baz(this);
}
}
コンパイラはother
が実際にthis
であることを必ず把握できますか?すべての場合ではありません。これはコンパイルすべきではないと主張することができますが、それは正しいインスタンスのプライベートインスタンスメンバーにアクセスできないコードパスがあることを意味しますさらに悪い。
オブジェクトレベルの可視性ではなく、タイプレベルのみを必要とすることで、問題が扱いやすくなり、実際に動作するshouldように見える状況を作る動作します。
[〜#〜] edit [〜#〜]:この推論は後方にあるというダニレ・ヒルガースのポイントにはメリットがあります。言語設計者は必要な言語を作成でき、コンパイラの作成者はそれに準拠する必要があります。そうは言っても、言語設計者には、コンパイラライターが自分の仕事をしやすくするためのインセンティブがあります。 (この場合、プライベートメンバーはthis
を介して(暗黙的または明示的に)onlyアクセスできると主張するのは十分に簡単です).
しかし、それが問題を必要以上に混乱させていると思います。ほとんどのユーザー(自分自身を含む)は、上記のコードが機能しなかった場合、それが不必要に制限されることに気付くでしょう:結局、それはmyアクセスしようとしているデータです!なぜthis
を通過する必要があるのですか?
要するに、コンパイラにとって「難しい」という主張を誇張しすぎたのではないかと思います。私が本当に理解したいのは、上記の状況はデザイナーが仕事をしたいと思うような状況のようだということです。
C#および同様の言語で使用されるカプセル化の種類 *の目的は、メモリ内の異なるオブジェクトではなく、異なるコード(C#およびJavaのクラス)の相互依存性を下げるためです。
たとえば、あるクラスで別のクラスの一部のフィールドを使用するコードを記述する場合、これらのクラスは非常に密に結合されます。ただし、同じクラスの2つのオブジェクトがあるコードを扱っている場合、余分な依存関係はありません。クラスは常にそれ自身に依存します。
ただし、カプセル化に関するこのすべての理論は、誰かがプロパティ(またはJavaでget/setのペア)を作成し、すべてのフィールドを直接公開するとすぐに失敗します。
*カプセル化の種類の説明については、Abelの優れた回答を参照してください。
この興味深いスレッドにはすでにかなりの回答が追加されていますが、whyの本当の理由はまったくわかりませんでした。 。試してみましょう:
80年代のSmalltalkと90年代半ばのJavaの間のどこかで、オブジェクト指向の概念が成熟しました。クラスのすべてのデータ(フィールド)がプライベートであり、すべてのメソッドがパブリックであるため、OO(1978年に最初に言及)のみが利用できる概念と考えられていなかった情報隠蔽がSmalltalkで導入されました。 90年代のOOの多くの新しい開発中に、Bertrand Meyerは彼の画期的な本でOO概念の多くを形式化しようとしました Object Oriented Software Construction(OOSC) =それ以来、OO概念および言語設計に関する(ほぼ)決定的な参照と見なされています。
Meyerによると、定義されたクラスのセット(192〜193ページ)でメソッドを使用可能にする必要があります。これにより、明らかに非常に高い粒度の情報隠蔽が可能になり、classAとclassBおよびそのすべての子孫で次の機能を使用できます。
_feature {classA, classB}
methodName
_
private
の場合、彼は次のように述べています:自身のクラスから見える型を明示的に宣言しないと、修飾された呼び出しでその機能(メソッド/フィールド)にアクセスできません。つまりx
が変数の場合、x.doSomething()
は許可されません。もちろん、クラス自体の内部では、無制限のアクセスが許可されます。
つまり、同じクラスのインスタンスによるアクセスを許可するには、そのクラスによるメソッドアクセスを明示的に許可する必要があります。これは、インスタンスプライベート対クラスプライベートと呼ばれることもあります。
クラスプライベート情報の非表示とは対照的に、インスタンスプライベート情報の非表示を使用する現在使用されている少なくとも2つの言語を知っています。 1つはEiffel、Meyerが設計した言語で、OOを最大限に活用します。もう1つはRubyで、現在でははるかに一般的な言語です。 Rubyでは、private
は次を意味します。 "このインスタンスにプライベート" 。
インスタンスプライベートを許可することは、コンパイラにとって難しいことが示唆されています。メソッドの修飾された呼び出しを許可または禁止することは比較的簡単なので、私はそうは思いません。プライベートメソッドの場合、doSomething()
は許可され、x.doSomething()
は許可されない場合、言語デザイナーはプライベートメソッドとフィールドに対してインスタンスのみのアクセシビリティを効果的に定義しました。
技術的な観点からは、いずれかの方法を選択する理由はありません(特に、Eiffel.NETがILを使用してこれを行うことができる場合、複数の継承がある場合でも、この機能を提供しない固有の理由はありません)。
もちろん、それは好みの問題であり、他の人が既に言及したように、プライベートメソッドとフィールドのクラスレベルの可視性の機能なしでは、かなり書きにくいメソッドもあります。
インスタンスのカプセル化に関するインターネットスレッド(言語がクラスレベルではなくインスタンスレベルでアクセス修飾子を定義するという事実を指すために使用されることがある用語)を見ると、この概念はしばしば眉をひそめます。ただし、一部の現代言語では、少なくともプライベートアクセス修飾子のためにインスタンスのカプセル化を使用していることを考えると、現代のプログラミングの世界でそれが可能であり、有用であると思われます。
ただし、C#の言語設計では、明らかにC++とJavaが最も難しく見えています。 EiffelとModula-3も写真にありましたが、Eiffelの多くの機能が欠けている(多重継承)ことを考えると、プライベートアクセス修飾子に関してはJavaとC++と同じルートを選択したと思います。
whyを本当に知りたい場合は、Eric Lippert、Krzysztof Cwalina、Anders Hejlsberg、または標準に取り組んでいる他の人を手に入れるようにしてください。 C#の。残念ながら、注釈付きの The C#Programming Language には明確な注釈が見つかりませんでした。
これは私の意見に過ぎませんが、実用的には、プログラマがクラスのソースにアクセスできる場合、クラスインスタンスのプライベートメンバーにアクセスすることで合理的に信頼できると思います。左側にすでに王国の鍵を与えたプログラマーの右手を縛るのはなぜですか?
理由は確かに、等価チェック、比較、クローン作成、演算子のオーバーロードです...たとえば、複素数にoperator +を実装するのは非常に難しいでしょう。
まず、プライベートの静的メンバーはどうなりますか?静的メソッドによってのみアクセスできますか? const
sにアクセスできなくなるため、これは望ましくありません。
明示的な質問については、それ自体のインスタンスのリンクリストとして実装されるStringBuilder
の場合を考えてください。
public class StringBuilder
{
private string chunk;
private StringBuilder nextChunk;
}
独自のクラスの他のインスタンスのプライベートメンバーにアクセスできない場合は、次のようにToString
を実装する必要があります。
public override string ToString()
{
return chunk + nextChunk.ToString();
}
これは機能しますが、O(n ^ 2)です-あまり効率的ではありません。実際、それはおそらく最初にStringBuilder
クラスを持つという目的全体を無効にします。 can独自のクラスの他のインスタンスのプライベートメンバーにアクセスする場合、適切な長さの文字列を作成し、各チャンクの安全でないコピーを実行することにより、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)であり、非常に高速であり、クラスの他のインスタンスのプライベートメンバーにアクセスできる場合のみ可能です。
これは多くの言語(C++の1つ)で完全に合法です。アクセス修飾子は、OOPのカプセル化の原則に基づいています。考え方は、outsideへのアクセスを制限することです。この場合、外部は他のクラスです。たとえば、C#のネストされたクラスは、その親のプライベートメンバーにもアクセスできます。
これは言語設計者にとっては設計上の選択です。このアクセスの制限は、エンティティの分離にあまり貢献せずに、いくつかの非常に一般的なシナリオを非常に複雑にする可能性があります。
同様の議論があります here
別のレベルのプライバシーを追加できなかった理由はないと思います。データは各インスタンスにプライベートです。実際、それは言語に完全性のいい感じさえ提供するかもしれません。
しかし、実際には、それが本当に役立つとは思いません。あなたが指摘したように、私たちの通常のプライベート性は、等値チェックや、Typeの複数のインスタンスを含む他のほとんどの操作などに役立ちます。ただし、OOPの重要なポイントであるデータの抽象化を維持することについてのあなたの意見も気に入っています。
このようにアクセスを制限する機能を提供することは、OOPに追加する素敵な機能になる可能性があると思います。本当にそんなに便利ですか?クラスは独自のコードを信頼できるはずなので、ノーと言います。そのクラスはプライベートメンバーにアクセスできる唯一のものであるため、別のクラスのインスタンスを処理するときにデータの抽象化を必要とする本当の理由はありません。
もちろん、いつでもコードを書くことができますif as privateをインスタンスに適用します。通常のget/set
データにアクセス/変更するメソッド。クラスが内部変更の対象となる可能性がある場合、おそらくコードはより管理しやすくなります。
上記の素晴らしい答え。この問題の一部は、そもそもクラス自体をインスタンス化することさえ許可されているという事実です。たとえば、再帰論理「for」ループでは、再帰を終了する論理がある限り、その種のトリックを使用することになります。しかし、そのようなループを作成せずに内部で同じクラスをインスタンス化または渡すと、広く受け入れられているプログラミングパラダイムであるにもかかわらず、論理的に独自の危険が生じます。たとえば、C#クラスは、デフォルトコンストラクターで自身のコピーをインスタンス化できますが、ルールを壊したり、因果ループを作成したりすることはありません。どうして?
ところで...この同じ問題は「保護された」メンバーにも当てはまります。 :(
このプログラミングパラダイムを完全に受け入れたことはありません。なぜなら、この問題のような問題が発生し、人々を混乱させ、プライベートメンバーを持つ理由をすべて無視するまで、ほとんどのプログラマーが完全に把握できない問題とリスクのスイート全体がまだあるためです。
この「奇妙で奇抜な」C#の側面は、優れたプログラミングが経験やスキルとは関係なく、トリックやトラップを知っているだけの理由の1つです。ルールは破られることを意図していたという議論は、どのコンピューティング言語にとっても非常に悪いモデルです。