Object.MemberwiseClone メソッド を使えばいいんだけど、これが protected なので外から呼び出せない。
仕方が無いので、MemberwiseClone をラップしたメソッドを定義する、こんな感じ。(ICloneable を使わなくてもよいかもしれません)
public abstract class 注文Data : ICloneable { public int Id { get; set; } public decimal 金額 { get; set; } public byte[] Timestamp { get; set; } #region ICloneable メンバ object ICloneable.Clone() { return this.MemberwiseClone(); } #endregion } public class 請求Data : 注文Data { public string 請求先 { get; set; } } public class 支払Data : 注文Data { public string 支払先 { get; set; } }
この場合、Clone メソッドの戻り値が Object 型なのでキャストが必要になってしまう。これは不便だという事で、次のようにしてみる。
public abstract class 注文Data : ICloneable { public int Id { get; set; } public decimal 金額 { get; set; } public byte[] Timestamp { get; set; } #region ICloneable メンバ object ICloneable.Clone() { return this.MemberwiseClone(); } #endregion } public class 請求Data : 注文Data { public string 請求先 { get; set; } public 請求Data Clone() { return ((ICloneable)this).Clone() as 請求Data; } } public class 支払Data : 注文Data { public string 支払先 { get; set; } public 支払Data Clone() { return ((ICloneable)this).Clone() as 支払Data; } }
これだと、派生クラス毎に Clone メソッドを実装しないといけない。メンドクサイ。てことで、ジェネリックを使ってみた。
public abstract class 注文Data<T> : ICloneable where T : class, ICloneable { public int Id { get; set; } public decimal 金額 { get; set; } public byte[] Timestamp { get; set; } #region ICloneable メンバ object ICloneable.Clone() { return this.MemberwiseClone(); } #endregion public T Clone() { return ((ICloneable)this).Clone() as T; } } public class 請求Data : 注文Data<請求Data> { public string 請求先 { get; set; } } public class 支払Data : 注文Data<支払Data> { public string 支払先 { get; set; } }
ジェネリックの type argument *1に、自身の型を渡すって使うのは初めて、なんか違和感ある。
でも、一番すっきりするのは、拡張メソッドかな?
public abstract class 注文Data : ICloneable { public int Id { get; set; } public decimal 金額 { get; set; } public byte[] Timestamp { get; set; } #region ICloneable メンバ object ICloneable.Clone() { return this.MemberwiseClone(); } #endregion } public class 請求Data : 注文Data { public string 請求先 { get; set; } } public class 支払Data : 注文Data { public string 支払先 { get; set; } } public static class Clone拡張 { public static T Clone<T>(this T source) where T : class, ICloneable { return source.Clone() as T; } }
そもそも MemberwiseClone しか使わないなら ICloneable とか実装するのも面倒だし、リフレクションで呼び出したらいいんじゃね?
public abstract class 注文Data { public int Id { get; set; } public decimal 金額 { get; set; } public byte[] Timestamp { get; set; } } public class 請求Data : 注文Data { public string 請求先 { get; set; } } public class 支払Data : 注文Data { public string 支払先 { get; set; } } public static class Clone拡張 { public static T Clone<T>(this T source) where T : class { return typeof(T).InvokeMember("MemberwiseClone", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.InvokeMethod, null, source, null) as T; } }
てことで、拡張メソッド内で リフレクション経由で呼び出す方法を取ることにしました。
実際これを使おうと思っている場所は、UnitTest で データだけのクラス *2を比較するために使いたいので、リフレクションでも良いかなと。こんな感じ
[Test] public void Hogeテストケース() { Hoge instance = new Hoge(〜); 請求Data expected = instance.請求.Clone(); instance.Hugo(); // 何かの処理を呼び出す。 請求Data acutal = instance.請求; // 何かの処理を呼び出した後も、Hoge クラスの 請求プロパティの中身は変更されていない事を確かめる。 Assert.AreEqual(expected.Id, actual.Id, "Id"); Assert.AreEqual(expected.金額, actual.金額, "金額"); Assert.AreEqual(expected.Timestamp, actual.Timestamp, "Timestamp"); Assert.AreEqual(expected.請求先, actual.請求先, "請求先"); }