C# のオブジェクトの一般的なリストがあり、そのリストを複製したいと考えています。リスト内のアイテムは複製可能ですが、実行するオプションはないようですlist.Clone()
。
これを回避する簡単な方法はありますか?
要素が値型の場合は、次のことができます。
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
、すべてのメンバーのディープ コピーを保証する必要があるため、私は避けます。代わりに、copy-constructor または のYourType.CopyFrom(YourType itemToCopy)
新しいインスタンスを返すようなファクトリ メソッドをお勧めしますYourType
。
これらのオプションはいずれも、メソッド (拡張など) でラップできます。
拡張メソッドを使用できます。
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 クラスの GetRange メソッドを使用できます。
List<int> oldList = new List<int>( );
// Populate oldList...
List<int> newList = oldList.GetRange(0, oldList.Count);
引用元:ジェネリックレシピ
public static object DeepClone(object obj)
{
object objResult = null;
using (var ms = new MemoryStream())
{
var bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Position = 0;
objResult = bf.Deserialize(ms);
}
return objResult;
}
これは、C# と .NET 2.0 で行う 1 つの方法です。オブジェクトは である必要があります[Serializable()]
。目標は、すべての参照を失い、新しいものを構築することです。
少し変更した後、クローンを作成することもできます。
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;
}
内のすべての単一オブジェクトの実際の複製が必要でない限りList<T>
、リストを複製する最良の方法は、コレクション パラメータとして古いリストを使用して新しいリストを作成することです。
List<T> myList = ...;
List<T> cloneOfMyList = new List<T>(myList);
myList
挿入や削除などの変更は影響cloneOfMyList
しません。その逆も同様です。
ただし、2 つのリストに含まれる実際のオブジェクトは同じです。
AutoMapper(または任意のマッピングライブラリ)を使用してクローンを作成するのは簡単で、多くのメンテナンスが可能です。
マッピングを定義します。
Mapper.CreateMap<YourType, YourType>();
魔法をかける:
YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);
値の型だけを気にする場合...
そして、あなたはタイプを知っています:
List<int> newList = new List<int>(oldList);
以前にタイプがわからない場合は、ヘルパー関数が必要になります。
List<T> Clone<T>(IEnumerable<T> oldList)
{
return newList = new List<T>(oldList);
}
ただ:
List<string> myNewList = Clone(myOldList);
プロジェクトで既に 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 class CloneableList<T> : List<T>, ICloneable where T : ICloneable
{
public object Clone()
{
var clone = new List<T>();
ForEach(item => clone.Add((T)item.Clone()));
return clone;
}
}
を使用してリストを配列に単純に変換し、 を使用しToArray
て配列を複製することもできArray.Clone(...)
ます。必要に応じて、Array クラスに含まれるメソッドでニーズを満たすことができます。
automapper を使用してオブジェクトをコピーします。1 つのオブジェクトをそれ自体にマップするマッピングを設定しただけです。この操作は、好きな方法でラップできます。
次のコードは、最小限の変更でリストに転送する必要があります。
基本的に、連続する各ループでより広い範囲から新しい乱数を挿入することで機能します。それ以上の数値がすでに存在する場合は、それらの乱数を 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つ、リフレクションを使用できます。これを適切にキャッシュすると、1,000,000 個のオブジェクトが 5.6 秒で複製されます (残念ながら、内部オブジェクトでは 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 万クラスの改善には疑いがあります。高速なプロトブフ/ニュートンを試してみます。
PS: 読みやすくするために、ここでは自動プロパティのみを使用しました。FieldInfo で更新するか、独自に簡単に実装する必要があります。
私は最近、DeepClone 関数をそのまま使用してProtocol Buffersシリアライザーをテストしました。100 万個の単純なオブジェクトでは 4.2 秒で勝っていますが、内部オブジェクトになると 7.4 秒という結果で勝っています。
Serializer.DeepClone(personList);
概要:クラスにアクセスできない場合は、これが役に立ちます。それ以外の場合は、オブジェクトの数に依存します。最大 10,000 個のオブジェクト (おそらく少し少ない) までリフレクションを使用できると思いますが、これを超えると、プロトコル バッファー シリアライザーのパフォーマンスが向上します。