tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板)
VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板)
[ツリー表示へ]  [ワード検索]  [Home]

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

- 関連一覧ツリー をクリックするとツリー全体を一括表示します)

古いスレッドにレスはつけられません。