白黒値型と参照型の違いを知っています。しかし、なぜ両方が必要なのでしょうか? 値型の代わりに参照型を使用できます。
5 に答える
実際、参照型しか知らない言語を構築することもできます。値型は、主に効率上の理由から存在します。たとえば、数値型と bool 型を値型として実装する方が効率的です。
値の型を理解するには、まず格納場所と参照型の概念を理解する必要があります。
通常、オブジェクトはフィールドを使用してその状態を表し、実行中のコードは通常、変数とパラメーターを使用してその状態を表します。このようなものは、アレイ スロットと同様に、まとめて「ストレージ ロケーション」と呼ばれます。
参照型の格納場所には、オブジェクト識別子、または特別な「null」値が保持されます。このような保管場所は、実際にはオブジェクトを保持するのではなく、オブジェクトをすばやく見つけるために使用できる識別子を保持するだけです。インターフェース型 (例: ) であると宣言された格納場所はICollection<T>
、指定されたインターフェース (または「null」) を実装することがわかっているオブジェクトへの参照を保持します。
参照型の格納場所をコピーすると、単に識別子のコピーが作成されます。参照先オブジェクトの物理的状態には何の影響もありません。概念的には、これは簡単に思えるかもしれませんが、難しい場合があります。問題は、参照型の格納場所が物理的にオブジェクト識別子を保持している一方で、意味的にはそのような格納場所を使用して、オブジェクトの次の側面のいずれかをキャプチャできることです。
- その ID (特に、同じオブジェクトへの参照を保持する他のストレージの場所に関連するため)
- その不変の特徴
- その可変特性
もちろん、物理的には、参照型の格納場所はオブジェクトの ID を保持します。それにもかかわらず、そのような場所は、他の情報を保持するために意味的に使用されることがよくあります。たとえば、オブジェクトがtypeGeorge
のフィールドを保持しているとします。このようなストレージの場所は、整数のコレクションへの参照を保持します。これにより、整数の列挙が可能になり、追加または削除が許可される場合と許可されない場合があります。の内容は の状態にどのように影響しますか? さまざまな形で州に影響を与える可能性があります。thing
ICollection<integer>
George
thing
- `George` は `thing` がある種の不変コレクションの ID を保持していることを期待するかもしれません -- 常に同じ数のセットを返す何か。同じ番号を含む不変のコレクションが複数存在する場合、どのコレクションが「thing」によって識別されるかによって、「George」の状態は影響を受けません。その状態は不変のコンテンツに依存します。
- `George` は、それが作成した変更可能なリストへの参照を `thing` に格納し、他のオブジェクトとは共有していない可能性があります。`George` は、`thing` によって参照されるリストがそこに置くものは何でも保持し、それ以外は何も保持しないことを期待しています。このシナリオでは、`George` は、そのコレクションが同じ可変特性を持つ別のコレクションに置き換えられても気にしません。そのコレクションの可変コンテンツに関心があるだけです。
- `George` は、`thing` によって参照されるコレクションの内容を気にしない可能性がありますが、その仕事は、その内容を気にかけている他のオブジェクトのために、そのコレクションに何かを追加することです。このシナリオでは、コレクションの ID が重要です。`George` が追加したものを参照することを期待しているオブジェクトと同じコレクションへの参照を `thing` が保持することが不可欠です。
- 最後に、George は変更可能なコンテンツと「thing」によって参照されるオブジェクトのアイデンティティの両方に関心がある可能性があります。このシナリオは、`George` が、そのコレクションが他のオブジェクトからアイテムを受け取ることを期待していたオブジェクトである場合に適用されます。
上記の 4 つのケースすべてで、格納場所の型がまったく同じであることに注意してください。その保管場所の宣言については、その保管場所のどの側面が重要であるかを示すものは何もありません。さらに、多くの複雑さは、クラス参照が可変状態と ID の両方をカプセル化することが多いという事実に起因することに注意してください。ストレージの場所が ID の概念をカプセル化していなければ、事態はずっと単純になります。
値のタイプを入力します。次のような値型の構造体であるストレージの場所がある場合:
構造体 Point3d { public double X、Y、Z; Point3d(ダブル X、ダブル Y、ダブル Z) { this.X = X; this.Y = Y; this.Z = Z; } }
その格納場所は、その構造体のフィールドの内容を保持します (この場合、フィールド、、およびの 3 つのdouble
値です。クラスにそのタイプのフィールドがある場合、そのフィールドによって表される状態は、そこに保持されている 3 つの数値になります。同等性が内容に依存する場合と依存しない場合があるクラス オブジェクトでは、構造体型にはそのような曖昧さはありません. 2 つの公開フィールド構造体 (PODS - プレーン オールド データ構造体と呼ばれることもあります) は、それらが同じ型であり、それらが対応する場合、同等です。フィールドは同等です。X
Y
Z
クラスは値型ができない多くのことを行うことができますが、そのような力は混乱、あいまいさ、および複雑さを生み出す可能性があります。たとえば、クラスの状態の「スナップショット」を取得したい場合、そのフィールドのどの側面がその状態にとって重要であるかを知る必要があります。クラス オブジェクトの状態が、参照を保持するLarry
オブジェクトの変更可能な状態に依存する場合、 の状態のスナップショットを取得するには、の状態のスナップショットも取得する必要があります (そして、そのスナップショットへの参照を格納する必要があります)。オリジナルが への参照を保持していた複製のすべてのフィールドで)。対照的に、公開フィールド構造体のコピーは簡単です。すべてのフィールドをコピーするだけです。Mike
Larry
Mike
Larry
Larry
Mike
公開フィールド構造体は、いくつかの制限を除いて、優れたデータ ホルダーになります。
- 構造体 (公開フィールドまたはプライベート フィールドのいずれか) が可変量のデータを保持できる唯一の方法は、データを保持し、決して変更されないオブジェクトへの参照を保持することです。構造体は、そのフィールドの 1 つがオブジェクトへの唯一の現存する参照を保持しているかどうかを知る方法がないため、参照が変更されないことを期待するものによって保持されているかどうかを知る方法はありません。
- 構造体が独自のメソッド内でそれ自体を変更することは可能ですが、コンパイラは元の構造体ではなく構造体のコピーに対して構造体メソッドを実行することがあります。これは、変更可能な構造を避ける理由として一般的に挙げられていますが、そのような問題は、自身を変更する構造にのみ影響します。フィールドを直接の外部変更に公開する構造には影響しません。
クラスの状態が 5,000 個の 3 次元点に依存するとします。Point3d
型がクラスになる場合は、Point3d
型を不変にするか、5,000 個のインスタンスへの参照のみを保持する必要があります。型が不変の場合、Point3d
それによって示される状態の側面を変更したいときはいつでも、まったく新しいPoint3d
インスタンスを作成し、古いインスタンスを破棄する必要があります。型が可変の場合、ポイントから外部コードに座標を公開したいときはいつでも、X
、Y
、およびのZ
値をコピーする必要があります。どちらのオプションもあまり快適ではないようです。
露出Point3d
フィールド構造体を作成すると、これらの問題が両方とも解消されます。aPoint3d
は、X
、Y
、およびZ
にすぎないため、5,000 個のオブジェクトの配列Point3d
は単純に 15,000 個の数値を保持します。いずれかPoint3d
を外部に公開すると、関連する 3 つの番号が自動的にコピーされます。例えばZ
a の座標をPoint
他のものに影響を与えずに変更したい場合、問題ありません。Point
が配列に格納されている場合は、次のようにして配列スロット 4 の Z 座標に 9.8 を追加できます。
MyArray[4].Z += 9.8;
が他のタイプのコレクションに格納されている場合Point
、状況は少し厄介ですが、それほど悪くはありません。
Point3d temp = MyArray[4]; temp.Z += 9.8; MyArray[4] = 一時;
Point3d
クラスだった場合よりもはるかに便利です。
見落としていた違いの 1 つは、値型がスタックに割り当てられ、参照型がヒープに割り当てられていることです。これにより、パフォーマンスに大きな違いが生じます (1 つは間接的にアクセスされるポインターであり、もう 1 つは値自体であるため)。
編集: コメントに示されているように、ヒープとスタックに関する私の言及は間違っています。正しいステートメントは、Eric Lippert がここで言ったとおりです。
「デスクトップ CLR 上の C# の Microsoft 実装では、値がラムダまたは匿名メソッドの閉じたローカル変数ではないローカル変数または一時であり、メソッド本体がそうでない場合、値の型はスタックに格納されます。イテレータ ブロックであり、ジッタは値を登録しないことを選択します。」
値の型はvalue
直接格納されます。
例えば:
//I and J are both of type int
I = 20;
J = I;
ここで、int
は値型です。つまり、上記のステートメントはメモリ内に 2 つの場所を作成します。値型のインスタンスごとに個別のメモリが割り当てられ、スタックに格納されます。スタックに値があるため、クイックアクセスを提供します。
Reference Typereference
は値に格納されます。
例えば:
Vector X, Y; //Object is defined. (No memory is allocated.)
X = new Vector(); //Memory is allocated to Object. //(new is responsible for allocating memory.)
X.value = 30; //Initialising value field in a vector class.
Y = X; //Both X and Y points to same memory location. //No memory is created for Y.
Console.writeline(Y.value); //displays 30, as both points to same memory
Y.value = 50;
Console.writeline(X.value); //displays 50.
注:変数が参照の場合、その値を null に設定することで、どのオブジェクトも参照していないことを示すことができます。
参照型はヒープに格納され、値がヒープに配置されるため、アクセスが比較的遅くなります。
それを説明する最も簡単な方法は次のとおりだと思います。
値型は不変の値を表します。どのオブジェクトであっても、1
常に であり1
、3.14159265359
常に です3.14159265359
。そして8/18/2012 17:23 UTC
、常に同じ正確な瞬間を表します。int
したがって、これらの値を使用して何千もの異なるDateTime
オブジェクトを作成でき、それらがどこにあり、どのように使用されても、それらは常にまったく同じになります。
ただし、仕様が同じであっても、参照型は必ずしも同じではありません。私が特定の住所に家を建てて、友人にその図面を渡すと、まったく同じ仕様、同じサイズ、さらには同じ造園と同じサイズの庭を備えた別の家を建てることができます。同じように見えても、2 つの家がまったく同じになることはありません。