浅いコピー (Shallow Copy) でいいから、楽に実装したい時

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.請求先, "請求先");
}

*1:type parameter と混同するので、英語表記。

*2:Value Object や DTO とか「よく」呼ばれている物。この二つは異なる物らしいが、詳しく知らないのでまとめて書いてます。