のにっき

【C#】クラス型変数をコピーする方法

C#で変数をコピーしたとき、
自分の想定では値をコピーしたものだと思っていたのに
いざ結果を見てみると、
コピーした変数の値を変更したら
コピー元の変数の値も変わっていた!

という経験が誰しもあると思います。
私だけじゃないと思います・・・たぶん
という事で、今回は参照型変数についての話と
参照型になるクラスの値をコピーする方法のご紹介です

参照型とは?intやstringとは違うの?

参照型と値型の違いについては別記事で語ってます。
以下のリンクをご確認ください
apuridasuo.hatenablog.com

参照型の変数を普通にコピーした時

では実際に参照型となる自作クラスをコピーしてみます

        /*******************************************
           コピーする自作クラス
        ********************************************/
        public class TestClass
        {
            public int _Id;
            public string _Name;
            public string[] _Array01;
            //=========================
            // 通常のコンストラクタ
            //=========================
            public TestClass()
            {
                _Id = 0;
                _Name = "";
                _Array01 = new string[2] { "", "" };
            }
        }

        /*******************************************
           コピー処理を行う関数
        ********************************************/
        private void Main()
        {
            // コピーする変数を作成
            TestClass TestClassBef = new TestClass();
            TestClassBef._Id = 1;
            TestClassBef._Name = "コピー前";
            TestClassBef._Array01[0] = "ま";
            TestClassBef._Array01[1] = "え";
            // コピーしてコピー後の変数の値を変えてみる
            TestClass TestClassCopy = TestClassBef;
            TestClassCopy._Id = 2;
            TestClassCopy._Name = "コピー後";
            TestClassCopy._Array01[0] = "あ";
            TestClassCopy._Array01[1] = "と";
        }

こちらが自作クラス「TestClass 」の型の変数「TestClassBef 」を作成後、
別の変数「TestClassCopy 」にコピーして、値を変更するソースです。
処理後の変数の中身をメッセージ出力した結果が以下になります

            // コピー前の変数の中身
            TestClassBef._Id = 2
            TestClassBef._Name = "コピー後"
            TestClassBef._Array01[0] = "あ"
            TestClassBef._Array01[1] = "と"
            // コピー後の変数の中身
            TestClassCopy._Id = 2
            TestClassCopy._Name = "コピー後"
            TestClassCopy._Array01[0] = "あ"
            TestClassCopy._Array01[1] = "と"

コピー前の変数も値が変わってしまっていますね。
これが参照型変数の特徴なんです。

クラス型の変数を別変数としてコピーする方法

コピー後の変数を変更しても、
コピー前の変数に影響を与えない方法をご紹介します。
自作クラスのコンストラクタを追加します!

        /****************************
           コピーしたいクラス
        *****************************/
        public class TestClass
        {
            public int _Id;
            public string _Name;
            public string[] _Array01;
            //====================
            // 通常のコンストラクタ
            //====================
            public TestClass()
            {
                _Id = 0;
                _Name = "";
                _Array01 = new string[2] { "", "" };
            }
            //====================
            // コピー用コンストラクタ
            //====================
            public TestClass(TestClass CopyClass)
            {
                _Id = CopyClass._Id;
                _Name = CopyClass._Name;
                _Array01 = new string[2] { "", "" };
                Array.Copy(CopyClass._Array01, _Array01, CopyClass._Array01.Length);
            }
        }

        /*******************************************
           呼び出す関数
        ********************************************/
        private void Main()
        {
            TestClass TestClassBef = new TestClass();
            TestClassBef._Id = 1;
            TestClassBef._Name = "コピー前";
            TestClassBef._Array01[0] = "ま";
            TestClassBef._Array01[1] = "え";
            // コンストラクタを使って変数をコピーする
            TestClass TestClassCopy = new TestClass(TestClassBef);
            TestClassCopy._Id = 2;
            TestClassCopy._Name = "コピー後";
            TestClassCopy._Array01[0] = "あ";
            TestClassCopy._Array01[1] = "と";
        }

以上がソースになります。
通常コピーで書いたソースと見比べてみてください。
まず自作クラスの方にコンストラクタを追加しています。
※1つのクラスに、引数を変えれば複数のコンストラクタを作成できます
自分のクラス型を引数にしたコンストラクタです。
このコンストラクタの中で、
コピー元の変数の中身を取得する処理を記入しましょう!
そして、コピーする変数「TestClassCopy」作成時の記述も変わってます
「new TestClass(TestClassBef);」と定義することで、
コピー前と違ったアドレスを持った空の変数ができます。
そして、作成したコンストラクタによってコピー前の中身を持ってきます。
ソースの実行結果が以下になります

            // コピー前の変数の中身
            TestClassBef._Id = 1
            TestClassBef._Name = "コピー前"
            TestClassBef._Array01[0] = "ま"
            TestClassBef._Array01[1] = "え"
            // コピー後の変数の中身
            TestClassCopy._Id = 2
            TestClassCopy._Name = "コピー後"
            TestClassCopy._Array01[0] = "あ"
            TestClassCopy._Array01[1] = "と"

ちゃんとコピー後変数の変更がコピー前の変数に影響を与えていません。
このように、
参照型の変数は手順を踏んでコピーを行わないと
簡単に想定外の不具合が起きます!

私のコピーの仕方はほんの一例でしかありません。
参照型というものは不具合に遭遇して経験を積まないと
ぱっと理解できるものじゃないと思います。
今後もバグらせまくると思いますので、
変なバグをやっちゃったらまとめたいと思います。