仕事で久々にプログラミングに集中していたところ、『値渡し』『参照渡し』という初歩的な事で躓きました。
DeepCopyで調べればいくつか実装方法は出てきたのですが、現在参画している案件ではJSONを利用しているため、JSONで解決することにしました。
コンソールアプリケーション版のサンプルを残しておきます。
環境
.NET Framwork 4.6.1
Newtonsoft.Json 12.0.1
JSONとは
いつも通り説明的なものは外部リンクに頼ります。
- Qiita @SotaSuzuki – JSONとは
- Developers.IO – 非エンジニアに贈る「具体例でさらっと学ぶJSON」
- Think IT – JSONってなにもの?
- 「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 – JSONとは
個人的には「どんなオブジェクトでも文字列として扱える」モノくらいに捉えています。
私がIT業界に入った頃の案件ではXMLで通信を行っていたりもしましたが、2年ほど前からは通信で扱うのは全て『JSON』になりました。
今では通信データを全て『JSON』で扱おうと考えるぐらいに楽です。
『Newtonsoft.Json』のインストール
JSONを利用するにあたり、便利なパッケージを取得しておきます。
『ソリューションエクスプローラー』から作成したプロジェクトの『参照』を右クリックします。
表示されたコンテキストメニューから『Nugetパッケージの管理』をクリックします。
『参照』タブから「Newtonsoft.Json」を選択し、右ペインの『インストール』ボタンをクリックします。
※右ペインでインストールするパッケージのバージョンを選択することが出来ますが、 今回は最新版である「12.0.1」を使用します。
確認ダイアログが表示されたら『OK』をクリックします。
パッケージのインストールが完了するとプロジェクトの『参照』に『Newtonsoft.Json』が追加されます。これでインストールは完了です。
余談~参照渡しとは~
また説明的なものは外部リンクに頼ります。
- るびま 日本Rubyの会 RubyistMagazine – 値渡しと参照渡しの違いを理解する
- Qiita @ur_kinsk – 値渡しと参照渡しと参照の値渡しと
- ++C++; // 未確認飛行 C – 参照渡し
- 「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 – 参照渡しとは
実際の話、どんなコードを書いたら遭遇するか、だけ書いておきます。
まず初めにクラスを定義します。
namespace DeepCopySample
{
public class DeepCopy
{
public string Copy1 { get; set; }
public string Copy2 { get; set; }
public string Copy3 { get; set; }
}
}
命名規則なんてものはいったん無視します!
次にMain関数に下記のコードを記述します。
using System;
namespace DeepCopySample
{
class Program
{
static void Main(string[] args)
{
// 作成したクラスのインスタンス化
DeepCopy original = new DeepCopy();
// 編集インスタンスの作成
DeepCopy variant = original;
// 編集インスタンスに値を設定
variant.Copy1 = "ジャック";
variant.Copy2 = "クイーン";
variant.Copy3 = "キング";
// オリジナルインスタンスを表示
Console.WriteLine($"オリジナルCopy1:{original.Copy1}");
Console.WriteLine($"オリジナルCopy2:{original.Copy2}");
Console.WriteLine($"オリジナルCopy3:{original.Copy3}");
// コンソールが閉じるのを待つ処理
Console.ReadLine();
}
}
}
上記のコードではDeepCopyクラスをインスタンス化(以下、オリジナルインスタンスとします)し、さらにDeepCopyクラスの別インスタンス(以下、編集インスタンスとします)にオリジナルインスタンスを与えています。
その後、編集インスタンスのプロパティの値を書き換え、最後にオリジナルインスタンスを表示します。
上記のコードを実行した結果がこちらになります。
オリジナルインスタンスのプロパティの値は書き換えていないのに、表示された値は編集インスタンスで書き換えた値となりました。
これが『参照渡し』です。
DeepCopyの実現
『参照渡し』を体験したところで、いざ本題のDeepCopyです。
DeepCopyに関してはまたもや外部リンクに頼ります。
- くろの雑記帳 – シャローコピーとディープコピーの違い (おすし可愛い…。)
下記のメソッドを作成します。
using Newtonsoft.Json;
namespace DeepCopySample
{
public class Utils
{
public static T DeepCopy<T>(T entity)
{
// 引数をシリアライズ
string json = JsonConvert.SerializeObject(entity);
// デシリアライズした値を返却
return JsonConvert.DeserializeObject<T>(json);
}
}
}
引数として受け取ったクラスを一度JSON文字列に変換し、JSON文字列からクラスに変換するだけのシンプルなコードです。
上記のメソッドを利用して下記のコードを作成しました。
using System;
namespace DeepCopySample
{
class Program
{
static void Main(string[] args)
{
// 作成したクラスのインスタンス化
DeepCopy original = new DeepCopy()
{
Copy1 = "ダイヤ",
Copy2 = "スペード",
Copy3 = "クラブ"
};
// 編集インスタンスの作成
DeepCopy variant = Utils.DeepCopy(original);
// 編集インスタンスに値を設定
variant.Copy1 = "ジャック";
variant.Copy2 = "クイーン";
variant.Copy3 = "キング";
// オリジナルインスタンスを表示
Console.WriteLine($"オリジナルCopy1:{original.Copy1}");
Console.WriteLine($"オリジナルCopy2:{original.Copy2}");
Console.WriteLine($"オリジナルCopy3:{original.Copy3}");
// コンソールが閉じるのを待つ処理
Console.ReadLine();
}
}
}
上記コードではオリジナルインスタンスの書き換えが行われていないことを確かめるために、オリジナルインスタンスのプロパティに値を設定しています。
実行結果がこちら。
オリジナルインスタンスのプロパティの値が書き換えられないことを確認できました。
まとめ
- 値が意図しないタイミングで書き換えられていたら『参照渡し』を疑う。
- 『参照渡し』を解消するには変数の使い方を見直すか、DeepCopyの使用を検討する。
- 『JSON』を利用することでDeepCopyをシンプルに実現できる。
「なぜ書き換わっている!?」という瞬間はいくらコードを見ても気付かない場合が多いので焦ります…。
以上です。
コメントを残す