C#ならではかな?不変型。変わらない良さ。変わるとき。

こんにちは。ヤマヤタケシです。

プログラミングは奥深いというかなんというか、またまた新しい概念と出会いました。
それは不変型、Immutableです。

書き換わってほしくないときに、間違って書き換わらないようにC++ではconstを使っていました。
しかし、C#のconstはC++と違ってあちこちにつけることはできません。

C#では値が定数というときだけconstを使います。
C++では定数はもちろん、関数の引数、クラスのメンバ関数、メンバ変数につけて読み取り専用にできます。

じゃあ、C#で引数とか、メンバ関数とか、メンバ変数を読み取り専用にするってどうすんのよ?

メンバ変数はreadonlyがありまーす!コンストラクタでのみ初期化できるんだぜ。ほっとしました。

次、関数の引数を読み取り専用にするには・・・、ありません。
え?
ないの?
ありません。
じゃあ、書籍「リファクタリング」でいうところの引数の群れをクラスにしたものを、関数内部で変更されないことを保証するにはどうするのか?

そのクラスを不変型にします。

なんやー、不変型ってなんやー?

immutableなクラスです。

しらんがなー。
不変型(immutable)はコンストラクタでのみ初期化できるクラスです。
C#のstringは不変型だったので、調べました。
たとえば、abc->ABCと大文字に変換する場合、こうなります。

直接、smallが書き換わるのではなく、書き換わった新しいインスタンスが返ってきます。
全ての関数がこんな感じでなにか書き換えたいような操作をする場合は新規作成になります。
不変型ならもとのインスタンスに影響はでないので安心です。

で、自分のクラスを不変型にしようとしたのですが、気が狂いそうになりました。

不変型にするにはインスタンスをコピーして、書き換えて返すのですが、コピーが問題になります。
インスタンスのコピーのためには、Cloneとコピーコンストラクタが必要です。

しかも、コピーの方法が浅いコピーと深いコピーがあって、浅いコピーはMemberwiceCloneのおかげで楽ができますが、深いコピーの場合はいちいち手書きする必要があるし、しかも実行コストが大きいし・・・という意外にしんどいことになります。
しかも、メンバーにクラス型がいると浅いコピーでは意図しない動作になるので、結局深いコピーが必要なの?

マジで?

問題を整理しましょう。
1. メンバーにクラスがあると浅いコピーでは意図しない動作になる。
2. 深いコピーはメンバーごとの実装が必要。漏れるかもしれない。
3. 深いコピーは実行時のコストが大きい。

ついに、解決方法が見つかりました!
それは、不変型の連鎖です。
不変型を作るには、不変型のメンバーしか使わないようにするのです。
プリミティブ型は値型なので不変型ですし。
不変型を使うと・・・・
1. メンバーのクラスを不変型にすると浅いコピーで意図通りに動作します。
2. 浅いコピーなので、MemberwiceCloneができるので実装が楽。漏れもない。
3. 浅いコピーなので実行コストは小さい。
完璧!
・・・じゃなかった。
インスタンスのメンバー変数の値を変更する度にメンバー変数単位で深いコピーが走ります。
丸ごとのコピーよりは実行コストが小さいですが、回数が多いと問題になります。

3種類のコピーの特徴はこんな感じです。

実装の手間 Cloneの実行コスト 操作コスト 予想外の変更
浅いコピー 小さい 小さい 小さい 発生する
深いコピー 大きい 大きい 小さい 発生しない
不変型連鎖 小さい 小さい 大きい 発生しない

今回の不変型の概念を確認するために書いたコードです。
コメントもいっぱい書いたので参考にしてください。

実行結果。
immutable-run-result

あ、そうそう、System.Collecions.Immutableは、標準では入っていないのでNuGetでインストールします。
system-collections-immutable

(追記!)
constがそもそもの同期なら、不変型よりも読み取り専用のインタフェースを書くべき!みたい。
今回の状況的には不変型が正しい選択だったと思うけども、IReadOnlyCollectionみたいなものあるし、IReadOnlyXXXXを実装すれば良いときも多々ありますね。

この記事を書いた後にこのスライドを発見しました。理解が深まります。超オススメです。

そんじゃまた。


C#ならではかな?不変型。変わらない良さ。変わるとき。” への3件のコメント

  1. ピンバック: C#的なクローン手法のコスト比較 | ヤマヤタケシのブログ

  2. ピンバック: C#的なクローンの各種コスト比較 | ヤマヤタケシのブログ

コメントを残す

メールアドレスが公開されることはありません。