- PR -

List(Of T) クラスのディープコピーについて

投稿者投稿内容
ani
常連さん
会議室デビュー日: 2007/05/17
投稿数: 44
投稿日時: 2008-02-15 20:22
こんにちは。
いつもお世話になっております。

Windows アプリケーションでインスタンスの中身をコピーしようとしています。
(じゃんぬねっとさんのサイトや当サイトを参考にさせていただきました。)
http://blogs.wankuma.com/jeanne/archive/2006/04/06/22272.aspx
http://blogs.wankuma.com/jeanne/archive/2006/04/07/22287.aspx
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?mode=viewtopic&topic=27051&forum=7&start=0

コード:

Public Class Personal : Implements System.ICloneable

Public Name As String
Public Address As String
Public Age As Integer

Public Function CloneCopy() As Personal
Return DirectCast(Me.Clone(), Personal)
End Function

Private Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone()
End Function

End Class

Public Class Company : Implements System.ICloneable

Public CompanyName As String
Public Employees As New List(Of Personal)

Public Function CloneCopy() As Company
Dim company As Company = DirectCast(Me.Clone(), Company)
Dim employees As New List(Of Personal)
For Each personal As Personal In Me.Employees
employees.Add(personal.CloneCopy())
Next
company.Employees = employees
Return company
End Function

Private Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone()
End Function

End Class


上記コードでインスタンスのコピーはできたのですが、List(Of T) クラスのディープコピーは

コード:

For Each personal As Personal In Me.Employees
employees.Add(personal.CloneCopy())
Next


のように要素を一つずつコピーするしか方法はないのでしょうか?
List(Of T) クラスが ICloneable を実装していないのは他に方法があるからではないのかと思っていたりもします。
(下記サイトで ICloneable は Obsoleted だというような記述もありますし・・・。)
http://bbs.wankuma.com/index.cgi?mode=al2&namber=11755&KLOG=25

現状のコードで特に問題があるわけではないのですが、後学のためにご教示いただければと思います。
よろしくお願いします。

<環境>
Windows XP, .NET Framework 2.0, Visual Studio 2005, Visual Basic

[ メッセージ編集済み 編集者: ani 編集日時 2008-02-15 22:44 ]

[ メッセージ編集済み 編集者: ani 編集日時 2008-02-15 22:44 ]

[ メッセージ編集済み 編集者: ani 編集日時 2008-02-16 10:22 ]
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2008-02-15 21:25
引用:

aniさんの書き込み (2008-02-15 20:22) より:

のように要素を一つずつコピーするしか方法はないのでしょうか?


DeepCopy したいのであれば参照になるものは潜ってコピーするしかないですね。

引用:

List(Of T) クラスが ICloneable を実装していないのは他に方法があるからではないのかと思っていたりもします。


このあたりの理屈はよくわかりません...

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
otf
ベテラン
会議室デビュー日: 2006/08/04
投稿数: 91
投稿日時: 2008-02-15 21:53
引用:

List(Of T) クラスが ICloneable を実装していないのは他に方法があるからではないのかと思っていたりもします。



仮に実装されていたとしても要素が参照型だったら参照がコピーされるだけでDeepCopyにはなりませんよ。

要素のコピーはListのコンストラクタでできますが、
もしそのとき要素の参照ではなく値をコピーしたいのであれば
要素の型をStructureにしてメンバの型もすべて値型にすることですね。

それが出来ないんであれば要素型のコピーメソッドを定義して(方法はどうであれ)
要素を一つづつコピーするしかないと思います。

[ メッセージ編集済み 編集者: otf 編集日時 2008-02-15 21:54 ]
ani
常連さん
会議室デビュー日: 2007/05/17
投稿数: 44
投稿日時: 2008-02-15 23:04
>じゃんぬねっとさん
レスありがとうございます。

引用:

じゃんぬねっとさんの書き込み (2008-02-15 21:25) より:
DeepCopy したいのであれば参照になるものは潜ってコピーするしかないですね。


そうですか。
分かりました。地道にコピーしようと思います。

引用:

このあたりの理屈はよくわかりません...


List(Of T) クラスは今回初めて使用したのですが、要素にIndexでしかアクセスできなかったりして少し不便だなあと感じました。
(Findメソッドでは条件を動的に指定できないですよね...。)
もちろんまだ私は勉強不足ですので、あくまで第一印象です。

どうもありがとうございました。
ani
常連さん
会議室デビュー日: 2007/05/17
投稿数: 44
投稿日時: 2008-02-15 23:33
>otfさん
レスありがとうございます。

引用:

otfさんの書き込み (2008-02-15 21:53) より:
仮に実装されていたとしても要素が参照型だったら参照がコピーされるだけでDeepCopyにはなりませんよ。


勘違いしていました。
じゃんぬねっとさんのサイトで参照型のメンバのクローンコピーの方法が記述されていたので Deep Copy できると思ったのですが、
よく考えると、 List(Of Person) クラスの Person が参照型でした。
お恥ずかしい限りです。

引用:

要素のコピーはListのコンストラクタでできますが、
もしそのとき要素の参照ではなく値をコピーしたいのであれば
要素の型をStructureにしてメンバの型もすべて値型にすることですね。

それが出来ないんであれば要素型のコピーメソッドを定義して(方法はどうであれ)
要素を一つづつコピーするしかないと思います。


参照型は一つずつしかコピーできないのですね。
(値型も内部的には一つずつコピーしているのだとは思いますが。)
地道にコピーしたいと思います。

どうもありがとうございました。
Azulean
大ベテラン
会議室デビュー日: 2008/01/04
投稿数: 123
お住まい・勤務地: 大阪府
投稿日時: 2008-02-16 08:08
引用:

(Findメソッドでは条件を動的に指定できないですよね...。)


動的の範囲によりますが、「ある値と等しいこと」という条件の場合、その「ある値」を動的に変えたいのであれば実現可能です。

C#であれば、次のようなコードになります。
コード:
class ComparingTest
{
    private readonly int _Expected;
    public ComparingTest(int expected)
    {
        _Expected = expected;
    }

    public bool Predicate(int value)
    {
        return (_Expected == value);
    }
}

static void Main(string[] args)
{
    List<int> list = new List<int>();
    for (int i = 5; 0 <= i; i--)
    {
        list.Add(i);
    }

    ComparingTest testCondition = new ComparingTest(1);
    int foundIndex = list.FindIndex(testCondition.Predicate);
    Console.WriteLine(foundIndex.ToString());
}

ani
常連さん
会議室デビュー日: 2007/05/17
投稿数: 44
投稿日時: 2008-02-16 12:54
>Azuleanさん
レスありがとうございます。

教えていただいた通りに以下のコードで List(Of T) クラスの要素を取り出すことができました。

コード:
Public Class Personal : Implements System.ICloneable

   Public Name As String
   Public Address As String
   Public Age As Integer

   Public Function CloneCopy() As Personal
      Return DirectCast(Me.Clone(), Personal)
   End Function

   Private Function Clone() As Object Implements System.ICloneable.Clone
      Return Me.MemberwiseClone()
   End Function

End Class

Public Class Company : Implements System.ICloneable

   Public CompanyName As String
   Public Employees As New List(Of Personal)
   
   Public Function GetPersonal(name As String) As Personal
      Dim comparePersonal As New ComparePersonal(name)
      Dim personal As Personal = Employees.Find(AddressOf comparePersonal.Predicate)
      Return personal.CloneCopy()
   End Function
   
   Public Function CloneCopy() As Company 
      Dim company As Company = DirectCast(Me.Clone(), Company)
      Dim employees As New List(Of Personal)
      For Each personal As Personal In Me.Employees
         employees.Add(personal.CloneCopy())
      Next
      company.Employees = employees
      Return company
   End Function

   Private Function Clone() As Object Implements System.ICloneable.Clone
      Return Me.MemberwiseClone()
   End Function
   
   Private Class ComparePersonal

      Public Sub New(ByVal name)
         Me.name = name
      End Sub

      Private ReadOnly name As String

      Public Function Predicate(ByVal personal As Personal) As Boolean
         Return personal.Name.Equals(Me.name)
      End Function

   End Class
   
End Class



比較用のクラスを作成しなければいけないのが少し手間ですが、For Each で回すよりはスッキリしました。

どうもありがとうございました。
ani
常連さん
会議室デビュー日: 2007/05/17
投稿数: 44
投稿日時: 2008-02-16 13:44
連続の書き込み失礼いたします。
皆様のおかげで希望とする実装ができるようになりました。
ありがとうございます。

ただ、最終的に実装したい以下の機能がうまく組み込めません。
どうかご教示下さい。

コード:
'メイン Form
Public Class Form1

   Private company As New Company()

   Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
      'company インスタンスに値をセットする(省略)
   End Sub

   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
      Dim personal As New Personal()
      If Me.company.GetPersonal("IT太郎", personal) Then
         Console.WriteLine(personal.Name)
         Console.WriteLine(personal.Address)
         Console.WriteLine(personal.Age.ToString())
      End If
   End Sub

End Class

'個人情報
Public Class Personal : Implements System.ICloneable
   '...
   '省略
   '...
End Class

'企業情報
Public Class Company : Implements System.ICloneable

   '...
   '省略
   '...
   
   Public Function GetPersonal(name As String, personal As Personal) As Boolean
      Dim comparePersonal As New ComparePersonal(name)
      Dim _personal As Personal = Employees.Find(AddressOf comparePersonal.Predicate)
      If _personal Is Nothing Then
         Return False
      Else
         personal = _personal.CloneCopy()
         Return True
      End If
   End Function
   
   '...
   '省略
   '...

End Class



以下で
コード:
Public Function GetPersonal(name As String, personal As Personal) As Boolean
      Dim comparePersonal As New ComparePersonal(name)
      Dim _personal As Personal = Employees.Find(AddressOf comparePersonal.Predicate)
      If _personal Is Nothing Then
         Return False
      Else
         personal = _personal.CloneCopy()
         Return True
      End If
End Function


personal = _personal.CloneCopy()
としても Form1 クラスの personal に値がセットされません。
personal を値渡ししているからだと思いますが、こういう場合は参照渡しするしかないのでしょうか?
下記では、 ref ではなくて out を使うべきとありますが VB.NET では ByRef しかないですよね。
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=33854&forum=7

どういう書き方がよいのでしょうか。

スキルアップ/キャリアアップ(JOB@IT)