忍者ブログ

VB.NET-TIPS などプログラミングについて

VB.NETのTIPS(小技集)を中心に、Javascript、PHP その他のプログラミングについて少し役に立つ情報を発信します。いわゆる個人的な忘備録ですが、みなさんのお役に立てれば幸いです。

クラスのコピーについて(Object.MemberwiseClone)

今回はクラスのコピーについて考えてみますが、以下の順で説明したいと思います。


■クラス変数のコピー

早速クラスのテストを以下のソースで行ってみます。
テスト用クラスとして Integer 型と String 型の2個のパブリック変数を持ち、 コンストラクタで値を設定するものとします。

    'テストクラス
    Class cTest
        'パブリック変数
        Public intData As Integer
        Public strData As String

        'コンストラクタ
        Sub New(ByVal P_intData As Integer, ByVal P_strData As String)
            Me.intData = P_intData
            Me.strData = P_strData
        End Sub
    End Class

    'ボタンクリックでテストクラスのテスト
    Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click
        'クラスの生成
        Dim cTst1 As cTest = New cTest(100, "AAAA")
        Dim cTst2 As cTest
        'クラス変数のコピー
        cTst2 = cTst1
        '[cTst1]のデータを変更
        cTst1.intData = 200
        cTst1.strData = "BBB"
        'クラスデータの中身を表示
        Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData)
        Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData)
    End Sub

実行結果がコンソールには以下様に表示されます。

cTst1 : nData=200, strData=BBB
cTst2 : nData=200, strData=BBB

クラス変数のコピーを行った後で、[cTst1]のデータを変更したつもりでも、[cTst2]のデータとして表示すると同じになってます。 これは、クラスの変数にはクラス生成されたデータ領域の参照が設定されるだけで、 クラス変数のコピーではその参照値(ポインタ)がコピーされるだけです。
結果、[cTst1]も[cTst2]も参照が指し示す実体は同じものとなり、 [cTst1]でデータにアクセスしても、[cTst2]でデータにアクセスしたことと同じになります。

どうしてもコピーしたい場合は、[cTst2]を新しいクラスで生成して、クラスの中身をそれぞれコピーすることはできます。 以下のソースでその例を示します。(しかし、これでは変数が多い場合は手間ですし、カッコよくありませんが...)

    'テストクラス
    Class cTest
        'パブリック変数
        Public intData As Integer
        Public strData As String

        'デフォルトのコンストラクタ
        Sub New()
        End Sub

        'コンストラクタ
        Sub New(ByVal P_intData As Integer, ByVal P_strData As String)
            Me.intData = P_intData
            Me.strData = P_strData
        End Sub
    End Class

    'ボタンクリックでテストクラスのテスト
    Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click
        'クラスの生成
        Dim cTst1 As cTest = New cTest(100, "AAAA")
        Dim cTst2 As cTest
        'クラスのコピー
        cTst2 = New cTest
        cTst2.intData = cTst1.intData
        cTst2.strData = cTst1.strData
        '[cTst1]のデータを変更
        cTst1.intData = 200
        cTst1.strData = "BBB"
        'クラスデータの中身を表示
        Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData)
        Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData)
    End Sub

実行結果がコンソールには以下様に表示されます。当然ですが、[cTst1]のデータと[cTst2]のデータは異なるものが表示されます。

cTst1 : nData=200, strData=BBB
cTst2 : nData=100, strData=AAAA

本当はクラス自体が配列のコピーの Clone メソッドの様なものを持てればすっきりするのですが。


■クラスの実体コピー(Object.MemberwiseClone)

クラスにコピーメソッドを実装する為にソースを変更します。 先ずクラスにクローン作成をサポートするClone メソッドを定義し Object.MemberwiseClone を使って現在のクラスオブジェクトの簡易コピーを返してやります。

    'テストクラス(Cloneメソッドを持つ)
    Class cTestWithClone
        Implements ICloneable
        'パブリック変数
        Public intData As Integer
        Public strData As String

        'コンストラクタ
        Sub New(ByVal P_intData As Integer, ByVal P_strData As String)
            Me.intData = P_intData
            Me.strData = P_strData
        End Sub

        'コピーメソッド
        Public Function Clone() As cTestWithClone
            Return MemberwiseClone()    '現在の System.Object の簡易コピー作成
        End Function
    End Class

    'ボタンクリックでテストクラスのテスト
    Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click
        'クラスの生成
        Dim cTst1 As cTestWithClone = New cTestWithClone(100, "AAAA")
        Dim cTst2 As cTestWithClone
        'クラスのクローンコピー
        cTst2 = cTst1.Clone
        '[cTst1]のデータを変更
        cTst1.intData = 200
        cTst1.strData = "BBB"
        'クラスデータの中身を表示
        Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData)
        Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData)
    End Sub

実行結果がコンソールには以下様に表示されます。

cTst1 : nData=200, strData=BBB
cTst2 : nData=100, strData=AAAA


これでほぼ目的は達成されるのですが、クラス内に参照型のデータが存在する場合、参照のみがコピーされるため注意が必要です。 (このあたりが、「簡易コピー」といわれる所以です。)


■クラスの実体コピー・参照型を含む場合(Object.MemberwiseClone)

クラスに参照型データとして Integer 型の配列を定義します。 例のコピーメソッドの実装では Object.MemberwiseClone を使って現在のクラスオブジェクトの簡易コピーを作成し、 作成できない参照型を個別にクローン生成してやります。

    'テストクラス(Cloneメソッドを持つ)
    Class cTestWithClone
        'パブリック変数
        Public intData As Integer
        Public strData As String
        Public arr() As Integer

        'コンストラクタ
        Sub New(ByVal P_intData As Integer, ByVal P_strData As String, ByVal P_arr() As Integer)
            Me.intData = P_intData
            Me.strData = P_strData
            Me.arr = P_arr
        End Sub

        'コピーメソッド
        Public Function Clone() As cTestWithClone
            '先ず簡易コピー作成
            Dim cTest As cTestWithClone = MemberwiseClone()
            '参照型データが設定済みならばクローン作製
            If MyClass.arr IsNot Nothing Then
                cTest.arr = MyClass.arr.Clone
            End If
            'コピーを返す
            Return cTest
        End Function
    End Class

    'ボタンクリックでテストクラスのテスト
    Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click
        'クラスの生成
        Dim cTst1 As cTestWithClone = New cTestWithClone(100, "AAAA", {100, 200, 300})
        Dim cTst2 As cTestWithClone
        'クラスのコピー
        cTst2 = cTst1.Clone
        '[cTst1]のデータを変更
        cTst1.intData = 200
        cTst1.strData = "BBBB"
        cTst1.arr(1) = 0
        'クラスデータの中身を表示
        Console.WriteLine("cTst1 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}",
                          cTst1.intData, cTst1.strData, cTst1.arr(0), cTst1.arr(1), cTst1.arr(2))
        Console.WriteLine("cTst2 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}",
                          cTst2.intData, cTst2.strData, cTst2.arr(0), cTst2.arr(1), cTst2.arr(2))
    End Sub

実行結果がコンソールには以下様に表示されます。

cTst1 : nData=200, strData=BBBB, arr(1)=100, arr(2)=0, arr(0)=300
cTst2 : nData=100, strData=AAAA, arr(1)=100, arr(2)=200, arr(0)=300


これで参照型データのコピーが出来ることが分かりました。 尚、参照型データが複数ある場合は、全てにおいて処理を繰り返す必要があります。

■クラスの実体コピー・ディープコピー(Serializable)

以下の記事にもありますが、クラスも1個のオブジェクトなのでクラスの実体をシリアル化して、別の実体にシリアル化を戻せば、 クラスのコピーができることになります。

各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize)

以下のソースに上記の記事内の関数を利用してクラスのコピーを行います。
クラスの宣言の先頭で <Serializable()> と記述し、シリアル化ができることを宣言しないと、関数でコピーができません。

    'テストクラス(シリアル化可能)
    <Serializable()>
    Class cTest
        'パブリック変数
        Public intData As Integer
        Public strData As String
        Public arr() As Integer

        'コンストラクタ
        Sub New(ByVal P_intData As Integer, ByVal P_strData As String, ByVal P_arr() As Integer)
            Me.intData = P_intData
            Me.strData = P_strData
            Me.arr = P_arr
        End Sub
    End Class

    'ボタンクリックでテストクラスのテスト
    Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click
        'クラスの生成
        Dim cTst1 As cTest = New cTest(100, "AAAA", {10, 20, 30})
        Dim cTst2 As cTest
        'クラスのコピー
        cTst2 = DeepCopy(cTst1)
        '[cTst1]のデータを変更
        cTst1.intData = 200
        cTst1.strData = "BBB"
        cTst1.arr(1) = 0
        'クラスデータの中身を表示
        Console.WriteLine("cTst1 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}",
                          cTst1.intData, cTst1.strData, cTst1.arr(0), cTst1.arr(1), cTst1.arr(2))
        Console.WriteLine("cTst2 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}",
                          cTst2.intData, cTst2.strData, cTst2.arr(0), cTst2.arr(1), cTst2.arr(2))
    End Sub

実行結果がコンソールには以下様に表示されます。

cTst1 : nData=200, strData=BBB, arr(1)=10, arr(2)=0, arr(0)=30
cTst2 : nData=100, strData=AAAA, arr(1)=10, arr(2)=20, arr(0)=30

関連する記事

各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize)











PR

コメント

コメントを書く