web-dev-qa-db-ja.com

C#と不変性と読み取り専用フィールド...嘘?

初期化(構築)後でも読み取り専用フィールドの値を変更する「方法」があるため、クラス内のすべての読み取り専用フィールドを使用しても、必ずしもそのクラスのインスタンスが不変になるとは限らないと人々が主張していることがわかりました。

どうやって?どのように?

だから私の質問は、スレッドで安全に使用できる「実際の」不変オブジェクトをC#で実際に使用できるのはいつかということです。

また、匿名型は不変オブジェクトを作成しますか?また、LINQは不変のオブジェクトを内部的に使用していると言う人もいます。どのくらい正確に?

44
WPF-it

あなたはそこに5つの質問をしました。私は最初のものに答えます:

クラス内にすべての読み取り専用フィールドがあるからといって、そのクラスのインスタンスが不変になるとは限りません。構築後でも読み取り専用フィールドの値を変更する「方法」があるからです。どうやって?

構築後に読み取り専用フィールドを変更することはできますか?

はい、読み取り専用のルールを破るのに十分信頼できる場合

それはどのように機能しますか?

プロセス内のユーザーメモリのすべてのビットは変更可能です。読み取り専用フィールドなどの規則により、特定のビットが不変に見える場合がありますが、十分に努力すると、それらを変更することができます。たとえば、不変のオブジェクトインスタンスを取得し、そのアドレスを取得して、生のビットを直接変更できます。そのためには、メモリマネージャの内部実装の詳細について、かなりの賢さと知識が必要になる場合がありますが、どういうわけか、メモリマネージャはそのメモリを変更できるので、十分に努力すれば可能です。十分に信頼されている場合は、「プライベートリフレクション」を使用して安全システムのさまざまな部分を破壊することもできます。

定義上、完全に信頼されたコードは安全システムのルールを破ることができます。それが「完全に信頼できる」という意味です。完全に信頼されたコードがプライベートリフレクションや安全でないコードなどのツールを使用してメモリ安全ルールを破ることを選択した場合、完全に信頼されたコードはそれを行うことができます。

しないでください。そうすることは危険で混乱を招きます。メモリ安全システムは、コードの正確さについて推論しやすくするように設計されています。故意にその規則に違反することは悪い考えです。

それで、「読み取り専用」は嘘ですか?さて、全員がルールに従えば、全員がケーキを1枚もらえると言ったとしましょう。ケーキは嘘ですか?その主張はではなく「ケーキのスライスを手に入れる」という主張です。それは、全員がルールに従えば、ケーキのスライスを手に入れるという主張です。誰かがあなたのスライスをだまして取った場合、あなたのためのケーキはありません。

クラスの読み取り専用フィールドは読み取り専用ですか?はい。ただし全員がルールに従っている場合のみ。したがって、読み取り専用フィールドは「嘘」ではありません。契約では、全員がシステムのルールに従っている場合、フィールドは読み取り専用であることが確認されます。誰かが規則に違反した場合、おそらくそうではありません。それでは、「全員がルールに従えば、フィールドは読み取り専用です」という文は嘘になりません。

あなたが尋ねなかったが、おそらく持っているべき質問は、構造体のフィールドの「読み取り専用」が「嘘」でもあるかどうかです。その質問に関するいくつかの考えについては、 不変の構造体にパブリック読み取り専用フィールドを使用することは機能しますか? を参照してください。構造体の読み取り専用フィールドは、クラスの読み取り専用フィールドよりもはるかに嘘です。

残りの質問については、質問ごとに5つの質問をするのではなく、質問ごとに1つの質問をすると、より良い結果が得られると思います。

104
Eric Lippert

編集:エリックが言及するようにリフレクションなどを使用するのではなく、システムの「内部」で動作するコードに集中しました。

フィールドの種類によって異なります。フィールド自体が不変タイプ(例:String)の場合は、問題ありません。 StringBuilderの場合、フィールド自体が値を変更しなくても、オブジェクトは表示して変更できます。これは、「埋め込まれている」ためです。オブジェクトは変更できます。

匿名タイプはまったく同じです。例えば:

var foo = new { Bar = new StringBuilder() };
Console.WriteLine(foo); // { Bar = }
foo.Bar.Append("Hello");
Console.WriteLine(foo); // { Bar = Hello }

したがって、基本的に、適切に不変にしたいタイプがある場合は、それが不変データのみを参照するようにする必要があります。

読み取り専用フィールドを持つことができるが、thisを再割り当てすることによってそれ自体を変更するメソッドを公開する構造体の問題もあります。このような構造体は、呼び出す正確な状況に応じて、多少奇妙な動作をします。良くない-それをしないでください。

Eric Lippertが書いた 不変性についてはかなり -ご想像のとおり、すべてゴールドです...読んでください:)(Ericがこの質問に対する答えを書いていることに気づいていませんでした。このビットを書いた。明らかに彼の答えも読んだ!)

14
Jon Skeet

エリックの答えは元の質問の範囲をはるかに超えており、答えさえしないという点で、私はそれを突き刺します:

読み取り専用ですか?さて、値型について話している場合、それは簡単です。値型が初期化されて値が与えられると、それは決して変更できません(少なくともコンパイラに関する限り)。

参照型で読み取り専用を使用することについて話すと、混乱が生じ始めます。この時点で、参照型の2つのコンポーネントを区別する必要があります。

  • オブジェクトが存在するメモリ内のアドレスを指す「参照」(変数、ポインタ)
  • 参照が指しているデータを含むメモリ

オブジェクトへの参照は、それ自体が値型です。 参照型で読み取り専用を使用する場合、オブジェクトへの参照を不変にし、オブジェクトが存在するメモリに不変性を強制しません。

ここで、値型と他のオブジェクトへの参照を含むオブジェクトについて考えてみましょう。これらのオブジェクトには、値型と他のオブジェクトへの参照が含まれています。すべてのオブジェクトのすべてのフィールドが読み取り専用になるようにオブジェクトを構成する場合、本質的には、必要な不変性を実現できます。

13
w.brian

Eric Lippert が説明しているように、読み取り専用とスレッドセーフは2つの異なる概念です。

8
Darin Dimitrov

おそらく、「People」はサードハンドを聞いていて、コンストラクターが「読み取り専用」フィールドに関して取得する特別な特権について十分に伝達していませんでした。それらは読み取り専用ではありません。

それだけでなく、それらは単一の割り当てではありません。クラスのコンストラクターは、フィールドを何度でも変更できます。すべて「ルール」を破ることなく。

コンストラクターは、他の任意のメソッドを呼び出して、パラメーターとして渡すこともできます。したがって、他のメソッドの場合は、そのフィールドが不変であるかどうかをまだ確認できません。これは、そのオブジェクトのコンストラクターによって呼び出された可能性があり、コンストラクターは完了するとすぐにフィールドを変更するためです。

あなたはそれを自分で試すことができます:

using System;
class App
{
    class Foo 
    {
        readonly int x;
        public Foo() {  x = 1; Frob(); x = 2; Frob(); }
        void Frob() { Console.WriteLine(x); }
    }
    static void Main()
    {
        new Foo();
    }
}

このプログラムは1、2を出力します。「x」はFrobが読み取った後に変更されます。

これで、単一のメソッド本体内で、値は一定である必要があります。コンストラクターは、modify-accessを他のメソッドまたはデリゲートに委任できません。したがって、メソッドが戻るまで、フィールドは安定している必要があると確信しています。

上記のすべてはクラスに関するものです。構造体はまったく別の話です。

0
Aaron