Long-Slow-Distance

Programming Notes with Unity

メニュー

[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

関連記事