タイトル | : Re: Collectionオブジェクトのコピーについて |
記事No | : 11799 |
投稿日 | : 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 はコピーしないなど)や 循環参照があった場合にどうするかなどは、個別に考慮する必要がありますが、 シリアライザによって特性が異なりますので、興味があれば調べてみてください。
|