VB.NETの「罠」を断つ:シャローコピーの悪夢と、真のディープコピー実装術
業務自動化ツールを開発する際、最も多くのエンジニアが涙を流すポイントはどこか。それは、「コピーしたはずのオブジェクトを書き換えたら、元のオブジェクトまで変わってしまった」という、参照渡しの罠です。
VB.NETにおけるクラスは「参照型」です。`Dim b = a` と書いた瞬間、あなたはオブジェクトを複製したのではなく、同じメモリ領域を指す「付箋」をもう一枚貼ったに過ぎません。この挙動を理解していないコードは、本番環境で予期せぬデータ破壊を引き起こす時限爆弾となります。
今回は、プロの現場で生き残るための「安全な複製(クローン)」戦略を伝授します。
—
1. なぜ「シャローコピー」は業務ツールを壊すのか
まず、`Object.MemberwiseClone()` が返す「シャローコピー」の限界を知ってください。これはメモリ上のバイナリをそのままコピーするだけです。クラス内に別のクラスやリスト(List型など)が含まれている場合、中身の参照先まではコピーされません。
結果として、元のオブジェクトとコピー先が、内部のリストを共有することになります。これがバグの温床です。
—
2. 実践的解法:シリアライゼーションによる完全コピー
`ICloneable` インターフェースを実装する方法もありますが、複雑なオブジェクトツリーでは管理が煩雑になります。業務自動化ツールにおいて最も堅牢かつ保守性が高いのは、「バイナリシリアライゼーション(またはJSONシリアライゼーション)」を用いたコピーです。
この手法なら、オブジェクトの階層構造を問わず、完全に独立したメモリ空間に複製を作成できます。
プロダクションコード:DeepClone拡張メソッド
以下は、どんなクラスでもコピー可能にする拡張メソッドです。`Newtonsoft.Json`(または標準の `System.Text.Json`)を利用するのが現代のベストプラクティスです。
Imports Newtonsoft.Json
Public Module ObjectExtensions
”’
”’
”’
”’ コピー元オブジェクト
”’
Public Function DeepClone(Of T)(ByVal source As T) As T
‘ nullチェックは基本中の基本
If source Is Nothing Then Return Nothing
‘ JSON文字列に変換し、それを再びオブジェクトに復元する
‘ これにより、参照先オブジェクトを含めた完全なクローンが生成される
Dim json As String = JsonConvert.SerializeObject(source)
Return JsonConvert.DeserializeObject(Of T)(json)
End Function
End Module
—
3. 実務における「注意点」と設計指針
この実装を導入するにあたって、現場のリーダーとして以下の3点を徹底してください。
- 循環参照に注意: シリアライズ手法は、AがBを持ち、BがAを持つような循環参照があると例外を吐きます。設計段階でクラスの依存関係を単一方向に保つ(あるいは `[JsonIgnore]` 属性で不要なプロパティを除外する)ことが不可欠です。
- パフォーマンスの天秤: 大量データを扱うループ内でこの処理を呼ぶと、シリアライズのコストが無視できません。データ量が多い場合は、必要なメンバだけをコピーする「コンストラクタコピー」を検討する勇気を持ってください。
- データベース連携: DBから取得したEntityをそのままコピーして操作し、最後に変更点のみを差分更新する設計は非常に強力です。しかし、UIとのバインディングを行っている場合、クローンしたオブジェクトと画面表示が同期しなくなる点には注意が必要です。
—
4. 最後に:なぜ「安易なコピー」を禁じるのか
業務自動化ツールは「保守性」が命です。後任のエンジニアがコードを読んだとき、「なぜここでわざわざコピーしているのか?」と疑問に思わせるような曖昧な実装は許されません。
「参照型である以上、明示的なディープコピーを行う」というルールをプロジェクトの標準にしてください。これにより、意図しない値の書き換えという、最もデバッグが困難なバグを根絶することができます。
コードは「動く」だけでなく、「読み手が誤解できない」状態で初めて完成します。
今日からあなたのコードに、この「境界線」を引く設計を取り入れてください。それが、プロのエンジニアの流儀です。
