タイトル : Re: Collectionオブジェクトのコピーについて 投稿日 : 2017/02/08(Wed) 20:03 投稿者 : 魔界の仮面弁士
> Private mintTestNo As String > Public Property TestNo() As Integer 接頭辞が「mint」なのに、As String なのですか? (^^; > Collectionオブジェクト そもそも Collection を使うのではなく、System.Collections.Generic 名前空間の ジェネリックなクラス (List や HashSet や Dictionary など)を使うことをお奨めします。 ついでに VB のバージョンアップもお奨めしておきます。 2008 以降であれば、"LINQ" を使うことができるため、 List や Dictionary や一次元配列の二次加工も容易になりますよ。 > CollectionオブジェクトはCloneメソッドもないようですし、 無ければ作りましょう。 シャローコピーにするかディープコピーにするかも貴方次第。 > データと構造を別インスタンスとしてコピーするにはどのようにしたら良いのでしょうか? たとえば、Collection に Add されていた TestItemA のインスタンス が、 Dim TestItemA As New Class1() ではなく、 Dim TestItemA As New System.IO.FileInfo("C:\Folder\File1.txt") だった場合を想像してみて下さい。 この場合、コレクションはどのようにコピーされるべきでしょうか? 複製された Collection に入っているべき FileInfo を考えてみると (A案) 元のコレクションに Add されていたのと同一の FileInfo インスタンス (B案) 同じファイル C:\Folder\File1.txt を参照した、別の FileInfo インスタンス (C案) ファイルコピーした C:\AnotherFolder\File1.txt へのFileInfo インスタンス のように、要件によっていろいろな考え方がありそうですよね。 しかし Collection は、特定の型向けに専用に用意されたコレクション型ではなく、 何でも入る汎用のコレクションです。 それぞれの要素をどのように複製するべきかを、Collection クラス側では分かりません。 ですから、Collection に Clone を持たせるというのも、やや酷な話と言えます。 なので、データをどのように複製するべきかという指示は、 あらかじめデータ側で提供しておく必要がある、というわけです。 言い換えれば、この場合に複製するのは Collection ではなく、Class1 の方だということです。 ここでは、複製に使える二種類の方法を記しておきます。 ========================================================================== 【案1】Class1 に、複製するためのメソッドを用意する方法 -------------------------------------------------------------------------- まずは Class1 に Public Function Clone() As Class1 Return CType(MemberwiseClone(), Class1) End Function を実装した上で、利用する際に TestCollection2.Add(TestItemA.Clone()) のようにします。 この場合、TestCollection1(1) と TestCollection2(1) は コピーされた別のオブジェクトとなりますので、 TestCollection1(1).TestData = "XYZ" と書き換えても、TestCollection2(1) は "AAA" のままになります。 なお、TestItemA だけを複製するのではなく、コレクション全体に対して適用したいのなら、 For Each o As Class1 In TestCollection1 TestCollection2.Add(o.Clone()) Next のように、個別に複製していけば OK です。 より丁寧に実装する場合、そのクラスが複製可能であることが明確となるよう、 ICloneable インターフェイスも Implements しておきましょう。 というのも、今回は Clone メソッドの実装を MemberwiseClone に任せていますが、 MemberwiseClone は、あくまでの簡易コピー(shallow copy)だからです。 値型の場合(もしくは値型のように振舞うクラス)、具体的には Integer や String などであれば、簡易コピーでも十分なのですが、 クラス(すなわち参照型)が相手の場合、参照がコピーされるだけであり、 参照先オブジェクトまではコピーされません。 たとえば「Class2 クラスのインスタンスを返すプロパティ」や 配列を返すメンバーがあった場合、Class2 や配列への『参照』が そのまま複写されてしまうことになるため、Class2 も同様に複製したり、 配列の各要素を複製したりと、再帰的に複製していく必要があります。 ただし、参照先オブジェクト(この場合は Class2)が System.ICloneable インターフェイスを 実装していた場合は、System.Object.MemberwiseClone メソッドを呼び出したときに、 自動的に ICloneable.Clone メソッドが利用されます。この場合は参照ではなく 参照先オブジェクトがコピーされる仕様です(deep copy)。 https://msdn.microsoft.com/ja-jp/library/system.object.memberwiseclone%28vs.90%29.aspx http://smdn.jp/programming/netfx/cloning/ ========================================================================== 【案2】シリアライズを用いる方法 -------------------------------------------------------------------------- まずは準備として、下記の名前空間をインポートしておきます。 Imports System.IO Imports System.Runtime.Serialization Imports System.Runtime.Serialization.Formatters.Binary 次に、Class1 に Serializable 属性を付与します。 <Serializable> Public Class Class1 これにより、Class1 がシリアライズ可能な(永続化可能な)クラスであると マークされることになります。 (ちなみに、Collection 自身にも Serializable 属性が付与されています) あとは、シリアライザを用いてこんな感じです。 今回は、BinaryFormatter というシリアライザを利用してみます。 '元データ Dim TestCollection1 As New Collection Dim TestItemA As New Class1() TestItemA.TestData = "AAA" TestItemA.TestNo = "1" TestCollection1.Add(TestItemA) '複製結果を入れるためのコレクションです 'これから複製結果を受け取るので、New しておく必要はありません Dim TestCollection2 As Collection Using stream As New MemoryStream() 'バイト配列をストリームとして扱うクラス Dim f As New BinaryFormatter() 'バイナリデータ形式でシリアライズするクラス 'コレクションをストリームオブジェクトにシリアル化します f.Serialize(stream, TestCollection1) '一度、読み取り位置を先頭に戻しておきます stream.Position = 0L 'シリアル化を解除します 'Deserialize メソッドにより、バイナリデータの入ったストリームから '元データを復元し、それを TestCollection2 変数に渡します TestCollection2 = CType(f.Deserialize(stream), Collection) End Using シリアライズは、フォーマット変換を伴うため、案1に比べると比較的低速です。 特に BinaryFormatter は、Deserialize に時間がかかることがあり、 データ量によっては、分単位で待たされることすらあります。 速度面が問題になる場合は、その他のシリアライザも試してみると良いかもしれません。 http://d.hatena.ne.jp/matarillo/20101207/p1 http://wannabe-note.com/1863 http://neue.cc/2010/05/29_261.html また、複製のために追加の処理が必要になるようなケースでは、さらに OnDeserialized / OnDeserializing / OnSerialized / OnSerializing 属性を 併用することも出来ます。 http://devlights.hatenablog.com/entry/20100330/p7 その他、複製したくないメンバーがある場合(たとえば TextBox なら Text の値はコピーしたいけど Handle はコピーしないなど)や 循環参照があった場合にどうするかなどは、個別に考慮する必要がありますが、 シリアライザによって特性が異なりますので、興味があれば調べてみてください。 |