構造体の防衛的コピー
構造体は値型であり、値への参照ではなく値そのものを持っている。
構造体型のフィールドに対してreadonlyを付ける(または読み取り専用(get-only)プロパティを使用する)と、構造体の値そのものが読み取り専用となり、フィールドの書き換えはできなくなる。
しかし、メソッドの呼び出しについては制限されない。そのため、メソッド内で値が変更されてしまう懸念がある。
実際には、このとき別の場所にフィールドの値がコピーされ、そのコピーがメソッドを呼ぶことになる。
結果として、もとの構造体は変更されずreadonlyが保たれる。
この挙動はメソッド内で値が書き換えられる可能性があるための措置であり、これを防衛的コピーという。
メソッド内で値を変更していなくても防衛的コピーは行われる。(コンパイラがメソッド内で値を変更しているかしていないかの区別ができないため)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public struct Point { public double X { get; set; } public double Y { get; set; } public Point(double x, double y) { X = x; Y = y; } public void ChangePoint(double x, double y) { X = x; Y = y; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
internal class Map { public string Name{ get; } public Point Location { get; } //読み取り専用 public Map(string name, Point location) { Name = name; Location = location; } //public void ChangeLocation(double x, double y) //{ //Location = new Point(3, 4); ReadOnlyなので構造体でもクラスでもエラー //Location.X = x; //構造体ではエラー、クラスではエラーにならない //Location.Y = y; //} public void ChangeLocation(double x, double y) { Point.ChangePoint(x, y);//メソッドは呼べる→読み取り専用なのに変更できてしまう? } public void Print() { Console.WriteLine($"{Location.X},{Location.Y}"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var point = new Point(5, 4); var map = new Map("学校", point); //pointを値渡しでmapを生成 map.Print(); //5,4 //map.Location = new Point(10, 2); 読み取り専用のためエラー //pointのプロパティに値をセット point.X = 2; point.Y = 2; map.Print(); //5,4 値渡しのため影響なし //pointの値を変更するメソッドを呼ぶ point.ChangePoint(3, 3); map.Print(); //5,4 値渡しのため影響なし //map.Locationの値を変更するメソッドを呼ぶ map.ChangeLocation(10, 10); map.Print(); //5,4 防衛的コピーにより影響なし //point自体の値は変わっている Console.WriteLine($"{point.X},{point.Y}"); //3,3 |
以下の例のようにMap2内でPointが生成される場合も、防衛的コピーは機能する。
メソッドを呼び出すとLocationがコピーされ、そのコピーからメソッドが呼ばれる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
internal class Map2 { public string Label { get; } public Point Location { get; } = new Point(5, 4);//読み取り専用 public Map2(string label) { Label = label; } public void ChangeLocation(double x, double y) { Location.ChangePoint(x, y);//メソッドは呼べる } public void Print() { Console.WriteLine($"{Location.X},{Location.Y}"); } } |
1 2 3 4 5 |
var map2 = new Map2("学校"); //Map2内でPointを生成 //map2.Locationを書き換えるメソッド map2.ChangeLocation(10, 10); map2.Print(); //5,4 //防衛的コピーにより変更の影響なし |
thisによる読み取り専用フィールの書き換え
構造体ではフィールドを読み取り専用にしていても、thisを置き換えることで書き換えできてしまう。
C# 7.2以降ではreadonly structが使えるので、構造体自体を読み取り専用にすることができる。
readonly structを使えば、thisも読み取り専用なるためthisによる書き換えの問題は起こらない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
internal struct Point2 { public double X { get; } //読み取り専用 public double Y { get; } //読み取り専用 public Point2(double x, double y) { X = x; Y = y; } public void ChangeThis(double x, double y) { //X = x;//読み取り専用なのでこれはかけない しかしThisでおきかえできてしまう //Y = y; this = new Point2(x, y); //Classではなく構造体の場合Thisは「自分自身の参照」 //ClassではReadOnlyの場合Thisに代入できないが //構造体だと代入できてしまう。 //→readonry structにすることで防げる。 } } |
コメント