次のコードでは、
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace clone_test_01
{
public partial class MainForm : Form
{
public class Book
{
public string title = "";
public Book(string title)
{
this.title = title;
}
}
public MainForm()
{
InitializeComponent();
List<Book> books_1 = new List<Book>();
books_1.Add( new Book("One") );
books_1.Add( new Book("Two") );
books_1.Add( new Book("Three") );
books_1.Add( new Book("Four") );
List<Book> books_2 = new List<Book>(books_1);
books_2[0].title = "Five";
books_2[1].title = "Six";
textBox1.Text = books_1[0].title;
textBox2.Text = books_1[1].title;
}
}
}
Book
オブジェクトタイプを使用してList<T>
を作成し、一意のタイトル(「1」から「5」)を付与するいくつかのアイテムを入力します。
次に、List<Book> books_2 = new List<Book>(books_1)
を作成します。
この時点から、リストオブジェクトのクローンであることがわかりますが、book_2
のブックオブジェクトは、books_1
のブックオブジェクトからの参照のままです。 books_2
の最初の2つの要素に変更を加え、TextBox
のbook_1
の同じ要素をチェックすることで証明されます。
books_1[0].title and books_2[1].title
はbooks_2[0].title and books_2[1].title
の新しい値に変更されました。
今の質問
List<T>
の新しいハードコピーを作成する方法books_1
とbooks_2
は互いに完全に独立するという考え方です。
Rubyがclone()
メソッドで行っているような、きちんとした高速で簡単なソリューションをMicrosoftが提供していなかったことに失望しています。
ヘルパーから本当に素晴らしいことは、私のコードを使用し、実行可能なソリューションでそれを変更して、コンパイルして動作できるようにすることです。この問題に対して提供されているソリューションを理解しようとする初心者にとって、本当に役立つと思います。
編集:Book
クラスはより複雑でプロパティが多いことに注意してください。物事をシンプルにしようとしました。
新しいBook
オブジェクトを作成してから、それらを新しいList
に入れる必要があります。
List<Book> books_2 = books_1.Select(book => new Book(book.title)).ToList();
更新:少し簡単です... List<T>
には、新しいリストを返す ConvertAll
というメソッドがあります。
List<Book> books_2 = books_1.ConvertAll(book => new Book(book.title));
Book
クラスに実装する汎用ICloneable<T>
インターフェイスを作成して、クラスが自身のコピーを作成する方法を認識できるようにします。
public interface ICloneable<T>
{
T Clone();
}
public class Book : ICloneable<Book>
{
public Book Clone()
{
return new Book { /* set properties */ };
}
}
その後、Markが言及したlinqまたはConvertAll
メソッドを使用できます。
List<Book> books_2 = books_1.Select(book => book.Clone()).ToList();
または
List<Book> books_2 = books_1.ConvertAll(book => book.Clone());
Rubyが
clone()
メソッドで行っているようなきちんとした、高速で簡単なソリューションをMicrosoftが提供していなかったことに失望しています。
ディープコピーを作成するnot以外は、シャローコピーを作成します。
ディープコピーでは、常に何をコピーするのか、常に注意する必要があります。考えられる問題の例を次に示します。
Book
にはAuthor
があり、Author
にはBook
sのリストがあります。Stream
を含めることができます。Window
のようなものである場合、これは特に問題になる可能性があります。現在、基本的に何かを複製する方法は2つあります。
Clone()
メソッドを実装します。 (ICloneable
インターフェースもありますが、notを使用する必要があります。Trevorが提案したカスタムICloneable<T>
インターフェースを使用しても大丈夫です。)必要なのがこのクラスの各フィールドの浅いコピーを作成することだけであることがわかっている場合、 MemberwiseClone()
を使用して実装できます。別の方法として、「コピーコンストラクター」を作成することもできます:public Book(Book original)
。MemoryStream
にシリアル化してから、逆シリアル化して戻します。これには、各クラスを[Serializable]
としてマークする必要があります。また、正確に何を(およびどのように)シリアル化するかを構成することもできます。しかし、これは「迅速で汚れた」ソリューションであり、ほとんどの場合パフォーマンスも低下します。List<Book> books_2 = new List<Book>(books_2.ToArray());
それはまさにあなたが望むことをする必要があります。 ここで説明します
まあ、
関係するすべてのクラスをシリアライズ可能としてマークすると、次のことができます。
public static List<T> CloneList<T>(List<T> oldList)
{
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, oldList);
stream.Position = 0;
return (List<T>)formatter.Deserialize(stream);
}
ソース:
Clone
はBookのオブジェクトインスタンスを返すため、ToList
を呼び出す前に、そのオブジェクトをBookにキャストする必要があります。上記の例を次のように記述する必要があります。
List<Book> books_2 = books_1.Select(book => (Book)book.Clone()).ToList();
Arrayクラスがニーズを満たしている場合は、List.ToArrayメソッドを使用して、要素を新しい配列にコピーすることもできます。
参照: http://msdn.Microsoft.com/en-us/library/x303t819(v = vs.110).aspx
これを使用できます:
var newList= JsonConvert.DeserializeObject<List<Book>>(list.toJson());
public static class Cloner
{
public static T Clone<T>(this T item)
{
FieldInfo[] fis = item.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
object tempMyClass = Activator.CreateInstance(item.GetType());
foreach (FieldInfo fi in fis)
{
if (fi.FieldType.Namespace != item.GetType().Namespace)
fi.SetValue(tempMyClass, fi.GetValue(item));
else
{
object obj = fi.GetValue(item);
if (obj != null)
fi.SetValue(tempMyClass, obj.Clone());
}
}
return (T)tempMyClass;
}
}