ジェネリックリストのディープコピーを作成しようとしています。コピーメソッドを作成し、実際に一度に1つずつ各メンバーをコピーする方法が他にあるかどうか疑問に思っています。次のようなクラスがあります。
_public class Data
{
private string comment;
public string Comment
{
get { return comment; }
set { comment = value; }
}
private List<double> traceData;
public List<double> TraceData
{
get { return traceData; }
set { traceData = value; }
}
}
_
上記のデータのリスト、つまり_List<Data>
_があります。私がやろうとしていることは、リストのサブセットのトレースデータをグラフにプロットすることです。画面に収まらないため、リストのすべてをプロットする必要はありません。
最初はList.GetRange()
メソッドを使用してリストのサブセットを取得しようとしましたが、_List<double>
_の下が深いコピーではなく浅いコピーされているようです。 List.GetRange()を使用して再度サブセットを取得すると、他の場所で取得された生データではなく、以前に変更されたデータが取得されます。
誰かがこれにどのように取り組むかについて私に指示を与えることができますか?どうもありがとう。
C#でこれにアプローチする慣用的な方法は、ICloneable
に Data
を実装し、ディープコピーを実行するClone
メソッドを記述することです(そして次に、おそらくEnumerable.CloneRange
メソッドを使用すると、リストの一部を一度に複製できます。これより簡単にするための組み込みのトリックやフレームワークメソッドはありません。
ただし、メモリとパフォーマンスが本当の問題でない限り、代わりに不変のData
オブジェクトを操作できるように再設計することをお勧めします。それははるかに簡単に終わります。
あなたはこれを試すことができます
public static object DeepCopy(object obj)
{
if (obj == null)
return null;
Type type = obj.GetType();
if (type.IsValueType || type == typeof(string))
{
return obj;
}
else if (type.IsArray)
{
Type elementType = Type.GetType(
type.FullName.Replace("[]", string.Empty));
var array = obj as Array;
Array copied = Array.CreateInstance(elementType, array.Length);
for (int i = 0; i < array.Length; i++)
{
copied.SetValue(DeepCopy(array.GetValue(i)), i);
}
return Convert.ChangeType(copied, obj.GetType());
}
else if (type.IsClass)
{
object toret = Activator.CreateInstance(obj.GetType());
FieldInfo[] fields = type.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
object fieldValue = field.GetValue(obj);
if (fieldValue == null)
continue;
field.SetValue(toret, DeepCopy(fieldValue));
}
return toret;
}
else
throw new ArgumentException("Unknown type");
}
DetoX83に感謝 記事 コードプロジェクト。
IClonableの方法が難しい場合。私は何かに変換して戻すことをお勧めします。 .Netの中で最速なので、BinaryFormatterまたはServicestack.TextのようなJsonコンバーターで実行できます。
コードは次のようになります。
MyClass mc = new MyClass();
string json = mc.ToJson();
MyClass mcCloned = json.FromJson<MyClass>();
mcClonedはmcを参照しません。
最も簡単な(しかし汚い)方法は、クラスによってICloneable
を実装し、次の拡張メソッドを使用することです。
public static IEnumerable<T> Clone<T>(this IEnumerable<T> collection) where T : ICloneable
{
return collection.Select(item => (T)item.Clone());
}
使用法:
var list = new List<Data> { new Data { Comment = "comment", TraceData = new List { 1, 2, 3 } };
var newList = list.Clone();
もう1つの方法は、クラスをserializable
としてマークし、バイナリシリアル化を使用することです。これが実際の例です
public class Program
{
[Serializable]
public class Test
{
public int Id { get; set; }
public Test()
{
}
}
public static void Main()
{
//create a list of 10 Test objects with Id's 0-10
List<Test> firstList = Enumerable.Range(0,10).Select( x => new Test { Id = x } ).ToList();
using (var stream = new System.IO.MemoryStream())
{
var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
binaryFormatter.Serialize(stream, firstList); //serialize to stream
stream.Position = 0;
//deserialize from stream.
List<Test> secondList = binaryFormatter.Deserialize(stream) as List<Test>;
}
Console.ReadKey();
}
}
オブジェクトを不変にした場合、オブジェクトのコピーを渡すことを心配する必要はありません。次のようなことができます。
var toPlot = list.Where(d => d.ShouldBePlotted());
オブジェクトを深くシリアル化する1つのquickおよびgenericの方法は、 JSON.net 。次の拡張メソッドを使用すると、任意のオブジェクトのリストをシリアル化できますが、Entity Frameworkのナビゲーションプロパティをスキップできます。これは、循環依存関係や不要なデータのフェッチにつながる可能性があるためです。
メソッド
public static List<T> DeepClone<T>(this IList<T> list, bool ignoreVirtualProps = false)
{
JsonSerializerSettings settings = new JsonSerializerSettings();
if (ignoreVirtualProps)
{
settings.ContractResolver = new IgnoreNavigationPropsResolver();
settings.PreserveReferencesHandling = PreserveReferencesHandling.None;
settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
settings.Formatting = Formatting.Indented;
}
var serialized = JsonConvert.SerializeObject(list, settings);
return JsonConvert.DeserializeObject<List<T>>(serialized);
}
用途
var clonedList = list.DeepClone();
デフォルトでは、JSON.NETはパブリックプロパティのみをシリアル化します。プライベートプロパティも複製する必要がある場合は、 このソリューション を使用できます。
このメソッドは、 オブジェクトの複雑な階層 の quick(de)serialization を可能にします。
コレクションは変更可能であるため、プログラムでディープコピーを実装する必要があります。
public class Data
{
public string Comment { get; set; }
public List<double> TraceData { get; set; }
public Data DeepCopy()
{
return new Data
{
Comment = this.Comment,
TraceData = this.TraceData != null
? new List<double>(this.TraceData)
: null;
}
}
}
Comment
フィールドはすでに不変のクラスであるため、浅くコピーできます。 TraceData
の新しいリストを作成する必要がありますが、要素自体は不変であり、それらをコピーするための特別な処理は必要ありません。
List.GetRange()を使用して再度サブセットを取得すると、他の場所で取得された生データではなく、以前に変更されたデータが取得されます。
新しいDeepCopy
メソッドを次のように使用します。
var pointsInRange = dataPoints
.Select(x => x.DeepCopy())
.GetRange(start, length);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DeepListCopy_testingSome
{
class Program
{
static void Main(string[] args)
{
List<int> list1 = new List<int>();
List<int> list2 = new List<int>();
//populate list1
for (int i = 0; i < 20; i++)
{
list1.Add(1);
}
///////
Console.WriteLine("\n int in each list1 element is:\n");
///////
foreach (int i in list1)
{
Console.WriteLine(" list1 elements: {0}", i);
list2.Add(1);
}
///////
Console.WriteLine("\n int in each list2 element is:\n");
///////
foreach (int i in list2)
{
Console.WriteLine(" list2 elements: {0}", i);
}
///////enter code here
for (int i = 0; i < list2.Count; i++)
{
list2[i] = 2;
}
///////
Console.WriteLine("\n Printing list1 and list2 respectively to show\n"
+ " there is two independent lists,i e, two differens"
+ "\n memory locations after modifying list2\n\n");
foreach (int i in list1)
{
Console.WriteLine(" Printing list1 elements: {0}", i);
}
///////
Console.WriteLine("\n\n");
///////
foreach (int i in list2)
{
Console.WriteLine(" Printing list2 elements: {0}", i);
}
Console.ReadKey();
}//end of Static void Main
}//end of class
}