[C#] クラスオブジェクトのディープコピー
C#のクラスオブジェクトをコピーしたい場合がまれによくある。
特にパラメータ系のクラスオブジェクトを作成して、元のオブジェクトの値は保持したままにして
渡した先であれこれしたい場合だ。
C#のクラスは参照での扱い方がベース(でありウリである所)なので、参照先で値をいじると元の方も変わってしまう。
それを避ける為には、元のクラスオブジェクトをコピーしたものを参照先に渡す必要がある。
C++ならmemcpyして終わりな問題でこれはこれでラクだったが、C#ではそうはいかない。
代わりに、浅いコピー(シャローコピー)と深いコピー(ディープコピー)の2通りのコピー方法がある。
シャローコピーの方は簡単で、Objectクラスに元々MemberwiseCloneというメソッドがあり、
これを使って目的のクラスにキャストするだけだ。
//-------------------------------------------------------------------------------- // 自身を複製したインスタンスを返す //-------------------------------------------------------------------------------- public HogeParam Clone() { return (HogeParam)MemberwiseClone(); }
ただし、MemberwiseCloneは文字通り浅いコピーであるという事に注意する必要がある。
もしクラス内に参照型の変数を持っている場合には、その参照先のクラスオブジェクトまでは複製されない。
あくまでも「参照先オブジェクトはこれです」という情報のみがコピーされるだけである。
もし参照型のメンバを持っているクラスの場合で、その参照先のオブジェクトも全て複製してコピーしたい場合を
ディープコピーと言うが、これを行うには自力で実装する必要がある。
こちらを参考にさせてもらって、汎用メソッド版と拡張メソッド版を以下のように用意した。
//-------------------------------------------------------------------------------- // 引数に渡したオブジェクトをディープコピーしたオブジェクトを生成して返す // ジェネリックメソッド版 //-------------------------------------------------------------------------------- public static T DeepCopy<T>( T target ) { T result; BinaryFormatter b = new BinaryFormatter(); MemoryStream mem = new MemoryStream(); try { b.Serialize(mem, target); mem.Position = 0; result = (T)b.Deserialize(mem); } finally { mem.Close(); } return result; } // 拡張メソッド版 public static object DeepCopy( this object target ) { object result; BinaryFormatter b = new BinaryFormatter(); MemoryStream mem = new MemoryStream(); try { b.Serialize(mem, target); mem.Position = 0; result = b.Deserialize(mem); } finally { mem.Close(); } return result; }
内容的には、BinaryFormatterによるシリアライズ、デシリアライズをMemoryStreamを介して行っているようなものか。
C#だとこの辺は少々めんどくさくなるが、致し方ない。
使用例はこんな感じ。
var hogeA = new HogeParam(); HogeParam hogeB; // ジェネリックメソッド版 hogeB = HogeUtil.DeepCopy( hogeA ); // 拡張メソッド版 hogeB = (HogeParam)hogeA.DeepCopy();
※
Unityでディープコピーを使用する際は、メンバ変数にMonoBehaviour継承のクラスといった
Unityで用意されている各コンポーネントクラスを持っている場合にはコピー出来ない(例外が発生する)ので注意。
(おそらくSystem.Serializable属性が指定されていない為と思われる。というかつけるわけがない・・・w)
参考:
http://smdn.jp/programming/netfx/cloning/
http://d.hatena.ne.jp/tekk/20091012/1255362429
http://d.hatena.ne.jp/tekk/20100131/1264913887