C#のオブジェクトの一般的なリストがあり、そのリストを複製したいと考えています。リスト内のアイテムは複製可能ですが、オプションがないようですlist.Clone()
。
これを回避する簡単な方法はありますか?
clone()
定義上、深いコピーではありませんか?C#では、=を使用してポインターを簡単に渡すことができると思いました。
C#のオブジェクトの一般的なリストがあり、そのリストを複製したいと考えています。リスト内のアイテムは複製可能ですが、オプションがないようですlist.Clone()
。
これを回避する簡単な方法はありますか?
clone()
定義上、深いコピーではありませんか?C#では、=を使用してポインターを簡単に渡すことができると思いました。
回答:
拡張メソッドを使用できます。
static class Extensions
{
public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
{
return listToClone.Select(item => (T)item.Clone()).ToList();
}
}
要素が値型の場合は、次のようにすることができます。
List<YourType> newList = new List<YourType>(oldList);
ただし、それらが参照型であり、詳細なコピーICloneable
が必要な場合(要素が適切に実装されていることが前提)、次のようにすることができます。
List<ICloneable> oldList = new List<ICloneable>();
List<ICloneable> newList = new List<ICloneable>(oldList.Count);
oldList.ForEach((item) =>
{
newList.Add((ICloneable)item.Clone());
});
明らかICloneable
に、上記のジェネリックを置き換えて、実装する要素タイプが何であれキャストしますICloneable
。
要素タイプがサポートしてICloneable
いないがコピーコンストラクターがある場合は、代わりにこれを行うことができます。
List<YourType> oldList = new List<YourType>();
List<YourType> newList = new List<YourType>(oldList.Count);
oldList.ForEach((item)=>
{
newList.Add(new YourType(item));
});
個人的にはICloneable
、すべてのメンバーの詳細なコピーを保証する必要があるため、私は避けます。代わりに、のYourType.CopyFrom(YourType itemToCopy)
新しいインスタンスを返すコピーコンストラクタまたはファクトリメソッドをお勧めしますYourType
。
これらのオプションはいずれも、メソッド(拡張子など)でラップできます。
ICloneable
定義されたときに、定義がクローンが深いか浅いかを決して述べていないため、オブジェクトがそれを実装するときにどのタイプのクローン操作が実行されるかを決定できないことです。つまり、のディープクローンを作成する場合は、それがディープコピーであることを確認List<T>
せずICloneable
に行う必要があります。
newList.AddRange(oldList.Select(i => i.Clone())
またはnewList.AddRange(oldList.Select(i => new YourType(i)
)
浅いコピーの場合は、代わりにジェネリックListクラスのGetRangeメソッドを使用できます。
List<int> oldList = new List<int>( );
// Populate oldList...
List<int> newList = oldList.GetRange(0, oldList.Count);
引用元:ジェネリックレシピ
List<int> newList = oldList.ToList()
。同じ効果。ただし、Arkiliknamのソリューションは、私の意見では可読性に最適です。
public static object DeepClone(object obj)
{
object objResult = null;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Position = 0;
objResult = bf.Deserialize(ms);
}
return objResult;
}
これは、C#および.NET 2.0でこれを行う1つの方法です。オブジェクトはである必要があります[Serializable()]
。目標は、すべての参照を失い、新しい参照を構築することです。
リストのクローンを作成するには、.ToList()を呼び出します。これにより、浅いコピーが作成されます。
Microsoft (R) Roslyn C# Compiler version 2.3.2.62116
Loading context from 'CSharpInteractive.rsp'.
Type "#help" for more information.
> var x = new List<int>() { 3, 4 };
> var y = x.ToList();
> x.Add(5)
> x
List<int>(3) { 3, 4, 5 }
> y
List<int>(2) { 3, 4 }
>
少し変更した後、クローンを作成することもできます:
public static T DeepClone<T>(T obj)
{
T objResult;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Position = 0;
objResult = (T)bf.Deserialize(ms);
}
return objResult;
}
if (!obj.GetType().IsSerializable) return default(T);
例外を防ぐ最初のステートメントとして追加できます。そして、それを拡張メソッドに変更する場合、エルビス演算子のようにvar b = a?.DeepClone();
(var a = new List<string>() { "a", "b" };
たとえば与えられた)を使用することもできます。
内のすべてのオブジェクトの実際のクローンが必要でない限りList<T>
、リストのクローンを作成する最良の方法は、古いリストをコレクションパラメータとして使用して新しいリストを作成することです。
List<T> myList = ...;
List<T> cloneOfMyList = new List<T>(myList);
myList
挿入や削除などの変更は影響せずcloneOfMyList
、その逆も同様です。
ただし、2つのリストに含まれる実際のオブジェクトは同じです。
プロジェクトですでにNewtonsoft.Jsonを参照していて、オブジェクトがシリアル化可能である場合は、いつでも使用できます。
List<T> newList = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(listToCopy))
おそらく最も効率的な方法ではありませんが、数百回から数百回実行しない限り、速度の違いに気付くことはないでしょう。
public static Object CloneType(Object objtype)
{
Object lstfinal = new Object();
using (MemoryStream memStream = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
binaryFormatter.Serialize(memStream, objtype); memStream.Seek(0, SeekOrigin.Begin);
lstfinal = binaryFormatter.Deserialize(memStream);
}
return lstfinal;
}
public List<TEntity> Clone<TEntity>(List<TEntity> o1List) where TEntity : class , new()
{
List<TEntity> retList = new List<TEntity>();
try
{
Type sourceType = typeof(TEntity);
foreach(var o1 in o1List)
{
TEntity o2 = new TEntity();
foreach (PropertyInfo propInfo in (sourceType.GetProperties()))
{
var val = propInfo.GetValue(o1, null);
propInfo.SetValue(o2, val);
}
retList.Add(o2);
}
return retList;
}
catch
{
return retList;
}
}
私の友人であるGregor Martinovicと私は、JavaScriptシリアライザーを使用したこの簡単なソリューションを思いつきました。クラスをSerializableとしてフラグを立てる必要はありません。テストでは、Newtonsoft JsonSerializerを使用してBinaryFormatterを使用するよりも高速にフラグを立てます。すべてのオブジェクトで使用できる拡張メソッド。
標準の.NET JavascriptSerializerオプション:
public static T DeepCopy<T>(this T value)
{
JavaScriptSerializer js = new JavaScriptSerializer();
string json = js.Serialize(value);
return js.Deserialize<T>(json);
}
Newtonsoft JSONを使用したより高速なオプション:
public static T DeepCopy<T>(this T value)
{
string json = JsonConvert.SerializeObject(value);
return JsonConvert.DeserializeObject<T>(json);
}
誰かがこれを読んだら幸いですが、Cloneメソッドで型オブジェクトのリストを返さないようにするために、インターフェイスを作成しました。
public interface IMyCloneable<T>
{
T Clone();
}
次に、拡張子を指定しました。
public static List<T> Clone<T>(this List<T> listToClone) where T : IMyCloneable<T>
{
return listToClone.Select(item => (T)item.Clone()).ToList();
}
そして、これが私のA / Vマーキングソフトウェアのインターフェイスの実装です。私のClone()メソッドがVidMarkのリストを返すようにしたかったのに対し(ICloneableインターフェイスは私のメソッドがオブジェクトのリストを返すことを望んでいました):
public class VidMark : IMyCloneable<VidMark>
{
public long Beg { get; set; }
public long End { get; set; }
public string Desc { get; set; }
public int Rank { get; set; } = 0;
public VidMark Clone()
{
return (VidMark)this.MemberwiseClone();
}
}
そして最後に、クラス内での拡張の使用法:
private List<VidMark> _VidMarks;
private List<VidMark> _UndoVidMarks;
//Other methods instantiate and fill the lists
private void SetUndoVidMarks()
{
_UndoVidMarks = _VidMarks.Clone();
}
誰もそれを好きですか?何か改善はありますか?
また、を使用してリストを配列に変換し、を使用して配列をToArray
複製することもできArray.Clone(...)
ます。ニーズによっては、Arrayクラスに含まれているメソッドがニーズを満たす場合があります。
あなたは拡張メソッドを使うことができます:
namespace extension
{
public class ext
{
public static List<double> clone(this List<double> t)
{
List<double> kop = new List<double>();
int x;
for (x = 0; x < t.Count; x++)
{
kop.Add(t[x]);
}
return kop;
}
};
}
たとえば、値型メンバーを使用してすべてのオブジェクトを複製できます。たとえば、次のクラスを検討してください。
public class matrix
{
public List<List<double>> mat;
public int rows,cols;
public matrix clone()
{
// create new object
matrix copy = new matrix();
// firstly I can directly copy rows and cols because they are value types
copy.rows = this.rows;
copy.cols = this.cols;
// but now I can no t directly copy mat because it is not value type so
int x;
// I assume I have clone method for List<double>
for(x=0;x<this.mat.count;x++)
{
copy.mat.Add(this.mat[x].clone());
}
// then mat is cloned
return copy; // and copy of original is returned
}
};
注:コピー(またはクローン)に変更を加えても、元のオブジェクトには影響しません。
同じ容量のクローンリストが必要な場合は、これを試すことができます:
public static List<T> Clone<T>(this List<T> oldList)
{
var newList = new List<T>(oldList.Capacity);
newList.AddRange(oldList);
return newList;
}
IClonableを実装しないアイテムのICollectionを変換する独自の拡張機能を作成しました
static class CollectionExtensions
{
public static ICollection<T> Clone<T>(this ICollection<T> listToClone)
{
var array = new T[listToClone.Count];
listToClone.CopyTo(array,0);
return array.ToList();
}
}
オートマッパーを使用してオブジェクトをコピーします。1つのオブジェクトをそれ自体にマッピングするマッピングをセットアップするだけです。この操作は好きなようにラップできます。
ディープコピーの場合、ICloneableが正しいソリューションですが、ICloneableインターフェイスの代わりにコンストラクターを使用するICloneableへの同様のアプローチを次に示します。
public class Student
{
public Student(Student student)
{
FirstName = student.FirstName;
LastName = student.LastName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
// wherever you have the list
List<Student> students;
// and then where you want to make a copy
List<Student> copy = students.Select(s => new Student(s)).ToList();
コピーを作成する次のライブラリが必要です
using System.Linq
System.Linqの代わりにforループを使用することもできますが、Linqはそれを簡潔でクリーンにします。同様に、他の回答が示唆しているように、拡張メソッドなどを作成することもできますが、その必要はありません。
次のコードは、最小限の変更でリストに転送されます。
基本的には、連続するループごとに、より大きな範囲から新しい乱数を挿入することで機能します。同じかそれ以上の数値がすでに存在する場合は、それらの乱数を1つ上にシフトして、ランダムインデックスの新しいより大きな範囲に転送されるようにします。
// Example Usage
int[] indexes = getRandomUniqueIndexArray(selectFrom.Length, toSet.Length);
for(int i = 0; i < toSet.Length; i++)
toSet[i] = selectFrom[indexes[i]];
private int[] getRandomUniqueIndexArray(int length, int count)
{
if(count > length || count < 1 || length < 1)
return new int[0];
int[] toReturn = new int[count];
if(count == length)
{
for(int i = 0; i < toReturn.Length; i++) toReturn[i] = i;
return toReturn;
}
Random r = new Random();
int startPos = count - 1;
for(int i = startPos; i >= 0; i--)
{
int index = r.Next(length - i);
for(int j = startPos; j > i; j--)
if(toReturn[j] >= index)
toReturn[j]++;
toReturn[i] = index;
}
return toReturn;
}
もう1つ:リフレクションを使用できます。これを適切にキャッシュすると、5.6秒で1,000,000個のオブジェクトのクローンが作成されます(残念ながら、内部オブジェクトでは16.4秒)。
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Person
{
...
Job JobDescription
...
}
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Job
{...
}
private static readonly Type stringType = typeof (string);
public static class CopyFactory
{
static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();
private static readonly MethodInfo CreateCopyReflectionMethod;
static CopyFactory()
{
CreateCopyReflectionMethod = typeof(CopyFactory).GetMethod("CreateCopyReflection", BindingFlags.Static | BindingFlags.Public);
}
public static T CreateCopyReflection<T>(T source) where T : new()
{
var copyInstance = new T();
var sourceType = typeof(T);
PropertyInfo[] propList;
if (ProperyList.ContainsKey(sourceType))
propList = ProperyList[sourceType];
else
{
propList = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
ProperyList.Add(sourceType, propList);
}
foreach (var prop in propList)
{
var value = prop.GetValue(source, null);
prop.SetValue(copyInstance,
value != null && prop.PropertyType.IsClass && prop.PropertyType != stringType ? CreateCopyReflectionMethod.MakeGenericMethod(prop.PropertyType).Invoke(null, new object[] { value }) : value, null);
}
return copyInstance;
}
Watcherクラスを使用して、簡単な方法で測定しました。
var person = new Person
{
...
};
for (var i = 0; i < 1000000; i++)
{
personList.Add(person);
}
var watcher = new Stopwatch();
watcher.Start();
var copylist = personList.Select(CopyFactory.CreateCopyReflection).ToList();
watcher.Stop();
var elapsed = watcher.Elapsed;
結果:内部オブジェクトPersonInstance-16.4、PersonInstance = null-5.6
CopyFactoryは、式の使用を含む多数のテストが含まれる、まさに私のテストクラスです。これを拡張機能などの別の形式で実装できます。キャッシングを忘れないでください。
私はまだシリアライズをテストしていませんでしたが、100万クラスの改善には疑問があります。高速なprotobuf / newtonを試してみます。
PS:わかりやすくするために、ここでは自動プロパティのみを使用しました。FieldInfoで更新することもできますが、自分で簡単に実装する必要があります。
最近、DeepClone関数を使用してプロトコルバッファシリアライザをテストしました。100万の単純なオブジェクトでは4.2秒で勝利しますが、内部オブジェクトでは7.4秒で勝利します。
Serializer.DeepClone(personList);
概要:クラスにアクセスできない場合は、これが役立ちます。それ以外の場合は、オブジェクトの数によって異なります。最大で10,000オブジェクト(多分少し少ない)のリフレクションを使用できると思いますが、それ以上の場合、プロトコルバッファーシリアライザーのパフォーマンスが向上します。
JSONシリアライザーとデシリアライザーを使用してC#でオブジェクトを複製する簡単な方法があります。
拡張クラスを作成できます。
using Newtonsoft.Json;
static class typeExtensions
{
[Extension()]
public static T jsonCloneObject<T>(T source)
{
string json = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(json);
}
}
複製してオブジェクト化するには:
obj clonedObj = originalObj.jsonCloneObject;