Come faccio a clonare un elenco generico in C #?

Ho una lista generica di oggetti in C # e desidero clonare la lista. Gli elementi all’interno della lista sono clonabili, ma non sembra esserci un’opzione per fare list.Clone() .

C’è un modo semplice per aggirare questo?

È ansible utilizzare un metodo di estensione.

 static class Extensions { public static IList Clone(this IList listToClone) where T: ICloneable { return listToClone.Select(item => (T)item.Clone()).ToList(); } } 

Se i tuoi elementi sono tipi di valore, puoi semplicemente:

 List newList = new List(oldList); 

Tuttavia, se sono tipi di riferimento e vuoi una copia profonda (supponendo che i tuoi elementi implementino correttamente ICloneable ), potresti fare qualcosa del genere:

 List oldList = new List(); List newList = new List(oldList.Count); oldList.ForEach((item) => { newList.Add((ICloneable)item.Clone()); }); 

Ovviamente, sostituisci ICloneable nei generics di cui sopra e lancia con qualunque sia il tuo tipo di elemento che implementa ICloneable .

Se il tuo tipo di elemento non supporta ICloneable ma ha un copy-constructor, puoi farlo invece:

 List oldList = new List(); List newList = new List(oldList.Count); oldList.ForEach((item)=> { newList.Add(new YourType(item)); }); 

Personalmente, ICloneable causa della necessità di garantire una copia profonda di tutti i membri. Invece, suggerirei il copy-constructor o un metodo factory come YourType.CopyFrom(YourType itemToCopy) che restituisce una nuova istanza di YourType .

Ognuna di queste opzioni potrebbe essere avvolta da un metodo (estensione o altro).

 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; } 

Questo è un modo per farlo con C # e .NET 2.0. Il tuo object richiede di essere [Serializable()] . L’objective è quello di perdere tutti i riferimenti e costruirne di nuovi.

Per una copia superficiale, è invece ansible utilizzare il metodo GetRange della class List generica.

 List oldList = new List( ); // Populate oldList... List newList = oldList.GetRange(0, oldList.Count); 

Citato da: Ricette generiche

Dopo una leggera modifica puoi anche clonare:

 public static T DeepClone(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; } 

Usa AutoMapper (o qualunque altra lib di mapping tu preferisci) per clonare è semplice e molto mantenibile.

Definisci la tua mapping:

 Mapper.CreateMap(); 

Fai la magia:

 YourTypeList.ConvertAll(Mapper.Map); 

Se ti interessano solo i tipi di valore …

E tu conosci il tipo:

 List newList = new List(oldList); 

Se non conosci il tipo precedente, avrai bisogno di una funzione di aiuto:

 List Clone(IEnumerable oldList) { return newList = new List(oldList); } 

Il giusto:

 List myNewList = Clone(myOldList); 

A meno che non sia necessario un vero clone di ogni singolo object all’interno del tuo List , il modo migliore per clonare un elenco è creare un nuovo elenco con il vecchio elenco come parametro di raccolta.

 List myList = ...; List cloneOfMyList = new List(myList); 

Le modifiche a myList come insert o remove non influenzeranno cloneOfMyList e viceversa.

Gli oggetti reali che contengono le due liste sono comunque gli stessi.

Per clonare un elenco basta chiamare .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() { 3, 4 }; > var y = x.ToList(); > x.Add(5) > x List(3) { 3, 4, 5 } > y List(2) { 3, 4 } > 

Se hai già fatto riferimento a Newtonsoft.Json nel tuo progetto e i tuoi oggetti sono serializzabili puoi sempre usare:

 List newList = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(listToCopy)) 

Forse non è il modo più efficiente per farlo, ma a meno che tu non lo stia facendo 100s of 1000s di volte potresti non notare nemmeno la differenza di velocità.

 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 : List, ICloneable where T : ICloneable { public object Clone() { var clone = new List(); ForEach(item => clone.Add((T)item.Clone())); return clone; } } 
  public List Clone(List o1List) where TEntity : class , new() { List retList = new List(); 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; } } 

È anche ansible convertire semplicemente l’elenco in un array utilizzando ToArray e quindi clonare l’array utilizzando Array.Clone(...) . A seconda delle esigenze, i metodi inclusi nella class Array potrebbero soddisfare le tue esigenze.

Puoi utilizzare il metodo di estensione:

 namespace extension { public class ext { public static List clone(this List t) { List kop = new List(); int x; for (x = 0; x < t.Count; x++) { kop.Add(t[x]); } return kop; } }; } 

Puoi clonare tutti gli oggetti usando i loro membri del tipo di valore, ad esempio, considera questa class:

 public class matrix { public List> 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 for(x=0;x 

Nota: se si apportano modifiche alla copia (o al clone), ciò non influirà sull'object originale.

Se hai bisogno di un elenco clonato con la stessa capacità, puoi provare questo:

 public static List Clone(this List oldList) { var newList = new List(oldList.Capacity); newList.AddRange(oldList); return newList; } 

Il mio amico Gregor Martinovic e io ci siamo inventati questa soluzione semplice usando un Serializer JavaScript. Non è necessario contrassegnare le classi come serializzabili e nei nostri test l’utilizzo di Newtonsoft JsonSerializer è ancora più rapido rispetto all’utilizzo di BinaryFormatter. Con i metodi di estensione utilizzabili su ogni object.

Opzione .NET JavascriptSerializer standard:

 public static T DeepCopy(this T value) { JavaScriptSerializer js = new JavaScriptSerializer(); string json = js.Serialize(value); return js.Deserialize(json); } 

Opzione più veloce con Newton JSON :

 public static T DeepCopy(this T value) { string json = JsonConvert.SerializeObject(value); return JsonConvert.DeserializeObject(json); } 

Ho creato una mia estensione che converte ICollection di elementi che non implementano IClonable

 static class CollectionExtensions { public static ICollection Clone(this ICollection listToClone) { var array = new T[listToClone.Count]; listToClone.CopyTo(array,0); return array.ToList(); } } 

Io uso l’automapper per copiare un object. Ho appena impostato una mapping che mappa un object su se stesso. Puoi concludere questa operazione come preferisci.

http://automapper.codeplex.com/

Il seguente codice dovrebbe trasferirsi in una lista con modifiche minime.

Fondamentalmente funziona inserendo un nuovo numero casuale da una gamma maggiore con ogni ciclo successivo. Se esistono già numeri uguali o superiori, sposta questi numeri casuali in alto di uno in modo che vengano trasferiti nella nuova gamma più ampia di indici casuali.

 // 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; } 

Un’altra cosa: potresti usare la riflessione. Se lo memorizzi correttamente, clonerà 1.000.000 di oggetti in 5,6 secondi (purtroppo 16,4 secondi con oggetti interni).

 [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 ProperyList = new Dictionary(); private static readonly MethodInfo CreateCopyReflectionMethod; static CopyFactory() { CreateCopyReflectionMethod = typeof(CopyFactory).GetMethod("CreateCopyReflection", BindingFlags.Static | BindingFlags.Public); } public static T CreateCopyReflection(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; } 

L’ho misurato in un modo semplice, usando la class 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; 

RISULTATO: con object interno PersonInstance - 16.4, PersonInstance = null - 5.6

CopyFactory è solo la mia class di test in cui ho dozzine di test incluso l'uso di espressioni. Potresti implementarlo in un'altra forma in un'estensione o altro. Non dimenticare il caching.

Non ho ancora testato la serializzazione, ma dubito di un miglioramento con un milione di classi. Proverò qualcosa di veloce protobuf / newton.

PS: per motivi di semplicità di lettura, ho usato solo la proprietà automatica qui. Potrei aggiornare con FieldInfo, o dovresti implementarlo facilmente da solo.

Recentemente ho testato il serializzatore di Protocol Buffers con la funzione DeepClone. Vince con 4,2 secondi su un milione di oggetti semplici, ma quando si tratta di oggetti interni, vince con il risultato di 7,4 secondi.

 Serializer.DeepClone(personList); 

RIEPILOGO: se non si ha accesso alle classi, questo sarà di aiuto. Altrimenti dipende dal conteggio degli oggetti. Penso che si possa usare la riflessione fino a 10.000 oggetti (forse un po 'meno), ma per di più il serializzatore di Protocol Buffers funzionerà meglio.

C’è un modo semplice per clonare oggetti in C # usando un serializzatore JSON e un deserializzatore.

È ansible creare una class di estensione:

 using Newtonsoft.Json; static class typeExtensions { [Extension()] public static T jsonCloneObject(T source) { string json = JsonConvert.SerializeObject(source); return JsonConvert.DeserializeObject(json); } } 

Per clonare e obiettare:

 obj clonedObj = originalObj.jsonCloneObject; 
  //try this List ListCopy= new List(OldList); //or try List ListCopy=OldList.ToList();