値を失うフィールドを持つ構造体があります。フィールドを静的に宣言でき、それで問題が解決します。また、構造体をクラスに変更するだけで (他に何も変更しない)、問題も解決します。これはなぜだろう?と思っただけです。
2 に答える
構造体は値渡しされます。つまり、構造体を渡すときは、その値のコピーを渡します。したがって、値のコピーを取得して変更すると、元の値は変更されていないように見えます。オリジナルではなく、コピーを変更しました。
あなたのコードを見ないと確信が持てませんが、これが起こっていることだと思います。
クラスは参照渡しであるため、これは発生しません。
これが、構造体が不変である必要がある理由であることに言及する価値があります。つまり、構造体が作成されると、その値は変更されません。変更されたバージョンを提供する操作は、新しい構造体を返します。
編集: 以下のコメントで、@supercat は変更可能なプロパティがより便利になることを示唆しています。ただし、構造体のプロパティ セッターも奇妙な失敗を引き起こす可能性があります。構造体がどのように機能するかを深く理解していない限り、驚くような例を次に示します。私にとっては、変更可能な構造体を完全に避けるのに十分な理由です。
次のタイプを検討してください。
struct Rectangle {
public double Left { get; set; }
}
class Shape {
public Rectangle Bounds { get; private set; }
}
さて、次のコードを想像してみてください:
myShape.Bounds.Left = 100;
意外かもしれませんが、これはまったく効果がありません!なんで?コードをより長く同等の形式に書き直してみましょう。
var bounds = myShape.Bounds;
bounds.Left = 100;
ここでは、 の値Bounds
がローカル変数にコピーされ、その値が変更される様子を簡単に確認できます。ただし、元の値がShape
更新されることはありません。
これは、すべてのパブリック構造体を不変にするための非常に説得力のある証拠です。自分が何をしているのかわかっている場合、可変構造体は便利ですが、個人的には、ネストされたプライベート クラスとしてその形式でのみ実際に使用します。
@supercat が指摘しているように、代替案は少し見苦しいです:
myShape.Bounds = new Rectangle(100, myShape.Bounds.Top,
myShape.Bounds.Width, myShape.Bounds.Height);
ヘルパー メソッドを追加する方が便利な場合があります。
myShape.Bounds = myShape.Bounds.WithLeft(100);
構造体が値渡しされると、システムは呼び出し先の構造体のコピーを作成するため、構造体の内容を確認し、おそらく自身のコピーを変更できますが、呼び出し元のコピーのフィールドに影響を与えることはできません。によって構造体を渡すこともできますref
。この場合、呼び出し先は呼び出し元の構造体のコピーを操作し、必要に応じて変更したり、ref
同様に実行できる他の関数に渡すこともできます。ただし、呼び出された関数が呼び出し元の構造体のコピーを他の関数で使用できるようにする唯一の方法は、それを渡すことref
であり、呼び出された関数は、構造体を渡したすべての関数まで戻ることができないことに注意してくださいref
も戻ってきました。したがって、呼び出し元は、関数呼び出しの結果として構造体に発生する可能性のある変更は、それが戻るまでに発生していることを保証できます。
この動作は、クラス オブジェクトとは異なります。関数が変更可能なクラス オブジェクトを別の関数に渡す場合、関数の実行が終了した後であっても、その別の関数がそのオブジェクトを即座に、または将来的に変更するかどうか、またはいつ変更するかを知る方法はありません。変更可能なオブジェクトが外部コードによって変更されないことを確認できる唯一の方法は、作成の瞬間から放棄されるまで、そのオブジェクトの唯一の所有者になることです。
セマンティクスを重視することに慣れていない人は、構造体を値で渡すと呼び出された関数にそのコピーが渡されるだけで、構造体の格納場所を別の場所に割り当てると構造体の内容がコピーされるだけであるという事実に最初は「驚く」かもしれません。値型が提供するものが非常に役立つことを保証します。は構造体であるため、 (assuming is an array) のようなステートメントは影響しますが、他の には影響しないPoint
ことがわかります。さらに、変更される唯一の方法は、 が別の配列に置き換えられるか、何かが に書き込まれる場合であると確信できます。対照的に、クラスであり、MyPoints[5].X += 1;
MyPoints
MyPoints[5].X
Point
MyPoints[5].X
MyPoints
MyPoints[5]
Point
MyPoint[5]
X
が外部の世界に公開されたことがある場合、前述のステートメントが型の他の格納場所のフィールド/プロパティに影響を与えるかどうかを知る唯一の方法は、型の格納場所またはコード内の任意の場所に存在するすべての格納場所Point
を調べることです。と同じインスタンスを指していました。コードが特定の型のすべての格納場所を調べる方法はないため、外部に公開されていた場合、そのような保証は不可能です。Point
Object
MyPoints[5]
Point[5]
ただし、構造体には厄介な問題が 1 つあります。一般に、システムはref
、呼び出されたコードが問題の構造体への書き込みを許可されている場合にのみ、構造体の通過を許可します。ただし、構造体メソッド呼び出しとプロパティ ゲッターはパラメーターthis
として受け取りref
ますが、上記の制限はありません。代わりに、読み取り専用構造体で構造体メソッドまたはプロパティ ゲッターを呼び出すと、システムは構造体のコピーを作成し、そのコピーをref
メソッドまたはプロパティ ゲッターに渡し、それを破棄します。システムは、メソッドまたはプロパティ ゲッターが mutate を試行するかどうかを知る方法がないためthis
、そのような場合に文句を言うことはありません。愚かなコードを生成するだけです。突然変異を避けるならthis
ただし、プロパティ セッター以外では (システムは読み取り専用構造でプロパティ セッターを使用することを許可しません)、問題を回避できます。