Home / ぼやきごと / 2011-05-08
2011-05-08

Clone メソッドのスマートな実装方法

C#…というか.NET Frameworkには ICloneable というインタフェースが定義されています。
Clone という自身の完全コピーなインスタンスを生成するメソッドを提供する、実装されていれば便利なインタフェースなのですが、一つ問題があります。

それは、クラスを継承する際の実装コストが高くなることです。
Clone メソッドを持つクラスを継承することを考えればわかりますが、継承先のクラスでは Clone メソッドを必ずオーバライドする必要があります(継承元で何らかの仕込みが行われている場合を除く)。
なぜなら Clone メソッドはそのままではベースクラスのインスタンスを返してしまうからです。

なので ICloneable インタフェースもしくはそれ相当の機能を実装する場合、 sealed なクラスでない限りは派生先のことを考慮した実装にする必要があります。
最も無難ですぐに思い付くのは派生クラスから利用可能なコピーコンストラクタを用意することですが、他にも色々と方法があります。
.NET Framework系言語で利用可能な方法については次のサイトでいくつか挙げられています。

ただ、上のサイトで挙げられている方法は、コピーコンストラクタを利用するものを除くと.NET Framework系言語以外では利用可能な言語が限られます。
シリアライズはJavaや boost::serialization を導入したC++でも使えますが、強引な手法だけに実行コストが高く付き、実用性は若干微妙です(クローン可能でない既存クラスのクローンを作成する方法としては便利かもしれませんが)。

まぁそんなわけで、結局のところコピーコンストラクタを使う方法が一番無難で、実質これしかない…と私は考えていました。
しかし、.NET Framework 3.0で導入された Freezable クラス(System.Windows 名前空間)がよりスマートな方法で Clone メソッドを実装していました。

Freezable クラスでは Clone メソッドは仮想メソッドではなく、派生クラスでオーバライドする必要もありません*1
また、コピーコンストラクタも不要です*2
その代わりに、 CreateInstanceCore メソッドと CloneCore メソッドをオーバライドする必要があります(前者は必須、後者は場合による)。

protected abstract Freezable CreateInstanceCore();
protected virtual void CloneCore(Freezable source);

CreateInstanceCore メソッドは CreateInstance メソッドから呼び出され、派生クラスにおいてそのクラス型の新しいインスタンスを生成します。
このメソッドは Freezable クラスから派生したクラスでは必ずオーバライドする必要があります。
とはいえ、 return new 派生クラス(); を書くだけなので実装コストは微々たるものです。

CloneCore メソッドは、引数に渡された派生クラス型のインスタンスの内容を自身に詳細コピーします。
派生クラスでフィールドを追加した場合、 base.CloneCore(source); を呼び出した上でそれらのフィールドもコピーするようにオーバライドする必要があります*3
要するにコピーコンストラクタ相当の処理をこのメソッドで行うわけです。

この2つのメソッドを用いると、 Freezable クラスの Clone メソッドは次のように書くことができます。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
-
|
!
-
-
|
!
|
-
!
|
|
!
// 実際の実装がこうなっているわけではなく、あくまでイメージです。
// なお、例外処理等は省略しています。
public Freezable Clone()
{
    // 内部で CreateInstanceCore メソッドが呼び出され、
    // 派生クラス型のインスタンスが生成される
    Freezable instance = CreateInstance();
 
    // 自身の内容を生成したインスタンスに詳細コピー
    instance.CloneCore(this);
 
    return instance;
}

これで Clone メソッドをオーバライドしなくとも派生先のクラスではそのクラス型のインスタンスを生成することができます。
.NET Framework固有の機能を用いているわけでもないので、クラスを扱える言語であれば大抵は実装可能です。

この方法がコピーコンストラクタを使う方法と比べて優れている点は次の通りです。

  • 派生先クラスで新たにコピーする必要のあるフィールドが無い場合、 CloneCore メソッドをオーバライドする必要が無い。
    • コピーコンストラクタを用いる方法では新たにコピーする必要のあるフィールドが無くともコピーコンストラクタと Clone メソッドを必ずオーバライドする必要がある。
  • 他のインスタンスの内容を自身にコピーする処理が任意のタイミングで使える。
    • コピーコンストラクタが使えるタイミングはインスタンス構築時のみ。

逆にコピーコンストラクタを使う方法と比べて厄介になる点は、 readonly なフィールドをコピーできないことでしょうか。
この方法を用いる場合、 readonly なフィールドは実質使えません。
もっとも get のみのプロパティを用いればいいのでさほど問題にはなりませんが(実装時にうっかり値を書き換えてしまうことを抑止できないくらい?)。

長々と書いた割にはそこまで圧倒的に優れている方法というわけではありませんが、まぁこんな方法もあるということで。

Category: [C#][プログラミング] - 2011-05-08 13:06:33

*1 .NET Frameworkでは戻り値を派生クラスの型とするためにメソッドの隠蔽を行っている場合があります。
*2 もちろん実装しても構いませんが、.NET Frameworkの Freezable クラスおよびその派生クラスではコピーコンストラクタは実装されていません。
*3 実際は必ずしもオーバライドする必要はないのですが、今回の趣旨とは外れるため説明は省きます。