Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9738

C#9.0から使えるrecordで独自のEqualsやDeepCloneを使いたい

$
0
0
レコード型とは レコード型というものを今日知りました。 MSDN https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/tutorials/records なぜ記事を書いたか プロパティをImmutableにするためにinit専用セッターを使いたいけど、DeepCloneするときにinit専用だと参照型の上書きができずに困ってしまいます。 そんな時にレコード型を使えばinit専用セッターを使いつつDeepCloneができそうだと思い書きました。 public sealed class Point : ICloneable { public double X { get; set; } public double Y { get; set; } public object Clone() { return (Point) MemberwiseClone(); } } public sealed class Enemy : ICloneable { public int Life { get; init; } public Point P { get; init; } // init public object Clone() { var clone = (Enemy) MemberwiseClone(); clone.P = (Point) P.Clone(); // !!!! initだと設定できん !!!! return clone; } } class Program { static void Main(string[] args) { var enemy = new Enemy() { Life = 100, P = new Point() {X = 0.000234, Y = 0.00000123,} }; var clone = (Enemy)enemy.Clone(); } } やったこと とりあえずclassをrecordに変更してみよ なんやと。「Cloneという名前のメンバーはレコードでは許可されていません。」 recordはCloneやEqualsをすでに定義しているらしい。 おなじみ++C++を見てね。 ++C++(レコード型) しょうがないかから適当にDeepClone用のIF作って実装しよ こうや。 public interface IDeepClone { object DeepClone(); } public sealed record Point : IDeepClone { public double X { get; set; } public double Y { get; set; } public object DeepClone() // Implementation of IDeepClone { return this with { }; } } public sealed record Enemy : IDeepClone { public int Life { get; init; } public Point P { get; init; } public object DeepClone() // Implementation of IDeepClone { return this with {P = this.P with { }}; // 中で保持しているrecordをwithで複製してDeepCloneしてる } } class Program { static void Main(string[] args) { var enemy = new Enemy() { Life = 100, P = new Point() {X = 0.000234, Y = 0.00000123,} }; var clone = enemy with { }; // コッチはShallow var deepClone = (Enemy)enemy.DeepClone(); // コッチはDeep enemy.P.X = 99999; Console.WriteLine($"Enemy :Life={enemy.Life}, P.X={enemy.P.X}, P.Y={enemy.P.Y}"); Console.WriteLine($"Clone :Life={clone.Life}, P.X={clone.P.X}, P.Y={clone.P.Y}"); Console.WriteLine($"DeepClone :Life={deepClone.Life}, P.X={deepClone.P.X}, P.Y={deepClone.P.Y}"); } } 念のため実行結果。 enemy.P.Xに99999設定してますが、Cloneには反映されてDeepCloneは反映されてない。 Enemy :Life=100, P.X=99999, P.Y=1.23E-06 Clone :Life=100, P.X=99999, P.Y=1.23E-06 DeepClone :Life=100, P.X=0.000234, P.Y=1.23E-06 浮動小数点とか完全一致じゃなくて、ちょっとの変更は同じにしちゃいたい(Equalsカスタマイズしたい) bool Equals(object obj)をoverrideできないのでbool Equals(T obj)を作った。 GetHashCodeも作った。 ↓こうや。 public interface IDeepClone { object DeepClone(); } public sealed record Point : IDeepClone { public double X { get; set; } public double Y { get; set; } public bool Equals(Point other) { if (Math.Abs(this.X - other.X) > 0.0001) return false; if (Math.Abs(this.Y - other.Y) > 0.0001) return false; return true; } public override int GetHashCode() { return new {X, Y}.GetHashCode(); } public object DeepClone() // Implementation of IDeepClone { return this with { }; } } public sealed record Enemy : IDeepClone { public int Life { get; init; } public Point P { get; init; } public object DeepClone() // Implementation of IDeepClone { return this with {P = this.P with { }}; // 中で保持しているrecordのインスタンスをwithで複製してDeepCloneしてる } } class Program { static void Main(string[] args) { { Console.WriteLine("検証1:withで複製した場合とDeepClone(値の変更無し)"); var enemy = new Enemy() { Life = 100, P = new Point() {X = 0.000234, Y = 0.00000123,} }; var clone = enemy with { }; var deepClone = (Enemy) enemy.DeepClone(); Console.WriteLine($"Enemy :Life={enemy.Life}, P.X={enemy.P.X}, P.Y={enemy.P.Y}"); Console.WriteLine($"Clone :Life={clone.Life}, P.X={clone.P.X}, P.Y={clone.P.Y}"); Console.WriteLine($"DeepClone :Life={deepClone.Life}, P.X={deepClone.P.X}, P.Y={deepClone.P.Y}"); Console.WriteLine($"元のインスタンスとwithで複製したインスタンスを比較 : {enemy.Equals(clone)}"); Console.WriteLine($"元のインスタンスとDeepCloneで複製したインスタンスを比較 : {enemy.Equals(deepClone)}"); Console.WriteLine(); } { Console.WriteLine("検証2:withで複製した場合とDeepClone(値を変更)"); var enemy = new Enemy() { Life = 100, P = new Point() {X = 0.000234, Y = 0.00000123,} }; var clone = enemy with { }; var deepClone = (Enemy) enemy.DeepClone(); enemy.P.X = 0.000999; Console.WriteLine($"Enemy :Life={enemy.Life}, P.X={enemy.P.X}, P.Y={enemy.P.Y}"); Console.WriteLine($"Clone :Life={clone.Life}, P.X={clone.P.X}, P.Y={clone.P.Y}"); Console.WriteLine($"DeepClone :Life={deepClone.Life}, P.X={deepClone.P.X}, P.Y={deepClone.P.Y}"); Console.WriteLine($"元のインスタンスとwithで複製したインスタンスを比較 : {enemy.Equals(clone)}"); Console.WriteLine($"元のインスタンスとDeepCloneで複製したインスタンスを比較 : {enemy.Equals(deepClone)}"); Console.WriteLine(); } { Console.WriteLine("検証3:withで複製した場合とDeepClone(値をちょっとだけ変更)"); var enemy = new Enemy() { Life = 100, P = new Point() {X = 0.000234, Y = 0.00000123,} }; var clone = enemy with { }; var deepClone = (Enemy) enemy.DeepClone(); enemy.P.X += 0.00004321; Console.WriteLine($"Enemy :Life={enemy.Life}, P.X={enemy.P.X}, P.Y={enemy.P.Y}"); Console.WriteLine($"Clone :Life={clone.Life}, P.X={clone.P.X}, P.Y={clone.P.Y}"); Console.WriteLine($"DeepClone :Life={deepClone.Life}, P.X={deepClone.P.X}, P.Y={deepClone.P.Y}"); Console.WriteLine($"元のインスタンスとwithで複製したインスタンスを比較 : {enemy.Equals(clone)}"); Console.WriteLine($"元のインスタンスとDeepCloneで複製したインスタンスを比較 : {enemy.Equals(deepClone)}"); Console.WriteLine(); } } 結果。Clone/DeepCloneの違いと、独自Equalsが効いていることが確認できた。 検証1:withで複製した場合とDeepClone(値の変更無し) Enemy :Life=100, P.X=0.000234, P.Y=1.23E-06 Clone :Life=100, P.X=0.000234, P.Y=1.23E-06 DeepClone :Life=100, P.X=0.000234, P.Y=1.23E-06 元のインスタンスとwithで複製したインスタンスを比較 : True 元のインスタンスとDeepCloneで複製したインスタンスを比較 : True 検証2:withで複製した場合とDeepClone(値を変更) Enemy :Life=100, P.X=0.000999, P.Y=1.23E-06 Clone :Life=100, P.X=0.000999, P.Y=1.23E-06 DeepClone :Life=100, P.X=0.000234, P.Y=1.23E-06 元のインスタンスとwithで複製したインスタンスを比較 : True 元のインスタンスとDeepCloneで複製したインスタンスを比較 : False 検証3:withで複製した場合とDeepClone(値をちょっとだけ変更) Enemy :Life=100, P.X=0.00027721, P.Y=1.23E-06 Clone :Life=100, P.X=0.00027721, P.Y=1.23E-06 DeepClone :Life=100, P.X=0.000234, P.Y=1.23E-06 元のインスタンスとwithで複製したインスタンスを比較 : True 元のインスタンスとDeepCloneで複製したインスタンスを比較 : True おわり

Viewing all articles
Browse latest Browse all 9738

Trending Articles