Come posso clonare un elenco generico in C #?


593

Ho un elenco generico di oggetti in C # e desidero clonare l'elenco. Gli elementi all'interno dell'elenco sono clonabili, ma non sembra esserci un'opzione da farelist.Clone() .

C'è un modo semplice per aggirare questo?


44
Dovresti dire se stai cercando una copia profonda o una copia superficiale
orip

10
Cosa sono le copie profonde e superficiali?
Colonnello Panic,


3
@orip Non è clone()per definizione una copia profonda? In C # puoi passare facilmente i puntatori con =, ho pensato.
Chris,

13
@Chris una copia superficiale copia un livello più in profondità della copia puntatore. Ad esempio, una copia superficiale di un elenco avrà gli stessi elementi, ma sarà un elenco diverso.
orip

Risposte:


385

È possibile utilizzare un metodo di estensione.

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

71
Penso che List.ConvertAll potrebbe farlo in un tempo più veloce, dal momento che può pre-allocare l'intero array per l'elenco, anziché dover ridimensionare tutto il tempo.
MichaelGG,

2
@MichaelGG, cosa succede se non vuoi convertire ma solo clonare / duplicare gli elementi nell'elenco? Funzionerebbe? || var clonedList = ListOfStrings.ConvertAll (p => p);
IbrarMumtaz,

29
@IbrarMumtaz: è lo stesso di var clonedList = new List <string> (ListOfStrings);
Brandon Arnold,

4
Bella soluzione! A proposito, preferisco l'elenco statico pubblico <T> CLone <T> ... È più utile in casi come questo, perché non è necessario un ulteriore cast: List <MyType> cloned = listToClone.Clone ();
Plutoz,

2
questa è una clonazione profonda
George Birbilis,

513

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

List<YourType> newList = new List<YourType>(oldList);

Tuttavia, se si tratta di tipi di riferimento e si desidera una copia approfondita (presupponendo che i propri elementi vengano implementati correttamente ICloneable), è possibile fare qualcosa del genere:

List<ICloneable> oldList = new List<ICloneable>();
List<ICloneable> newList = new List<ICloneable>(oldList.Count);

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

Ovviamente, sostituisci ICloneablei generici sopra e lancia con qualunque sia il tuo tipo di elemento implementato ICloneable.

Se il tuo tipo di elemento non supporta ICloneablema ha un costruttore di copia, puoi invece farlo:

List<YourType> oldList = new List<YourType>();
List<YourType> newList = new List<YourType>(oldList.Count);

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

Personalmente, eviterei a ICloneablecausa della necessità di garantire una copia approfondita di tutti i membri. Invece, suggerirei il costruttore di copia o un metodo factory come YourType.CopyFrom(YourType itemToCopy)quello restituisce una nuova istanza diYourType .

Ognuna di queste opzioni potrebbe essere racchiusa in un metodo (estensione o altro).


1
Penso che List <T> .ConvertAll potrebbe sembrare più bello che creare un nuovo elenco e fare un foreach + add.
MichaelGG,

2
@Dimitri: No, non è vero. Il problema è, quando è ICloneablestato definito, la definizione non ha mai dichiarato se il clone fosse profondo o superficiale, quindi non è possibile determinare quale tipo di operazione Clone verrà eseguita quando un oggetto lo implementa. Questo significa che se vuoi fare un clone profondo List<T>, dovrai farlo senza ICloneableessere sicuro che sia una copia profonda.
Jeff Yates,

5
Perché non usare il metodo AddRange? ( newList.AddRange(oldList.Select(i => i.Clone())o newList.AddRange(oldList.Select(i => new YourType(i))
phoog

5
@phoog: penso che sia un po 'meno leggibile / comprensibile durante la scansione del codice, tutto qui. La leggibilità vince per me.
Jeff Yates,

1
@JeffYates: Una ruga poco considerata è che le cose in genere devono essere copiate solo se esiste un percorso di esecuzione che le muterebbe. È molto comune che i tipi immutabili contengano un riferimento a un'istanza di tipo mutabile, ma non espongono mai tale istanza a nulla che possa mutarlo. Copiare inutilmente cose che non cambieranno mai può a volte essere un grosso consumo di prestazioni, aumentando l'utilizzo della memoria per ordini di grandezza.
supercat,

84

Per una copia superficiale, puoi invece utilizzare il metodo GetRange della classe List generica.

List<int> oldList = new List<int>( );
// Populate oldList...

List<int> newList = oldList.GetRange(0, oldList.Count);

Citato da: Ricette Generics


43
È inoltre possibile ottenere ciò utilizzando il contrector dell'elenco <T> per specificare un elenco <T> da cui copiare. es. var shallowClonedList = new List <MyObject> (originalList);
Arkiliknam,

9
Lo uso spesso List<int> newList = oldList.ToList(). Stesso effetto. Tuttavia, secondo me la soluzione di Arkiliknam è la migliore per la leggibilità.
Dan Bechard,

82
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 oggetto richiede di essere [Serializable()]. L'obiettivo è perdere tutti i riferimenti e costruirne di nuovi.


11
+1 - mi piace questa risposta - è veloce, sporco, cattivo e molto efficace. Ho usato in Silverlight e ho usato DataContractSerializer poiché BinarySerializer non era disponibile. Chi ha bisogno di scrivere pagine di codice di clonazione di oggetti quando puoi farlo? :)
slugster

3
Mi piace questo. Mentre è bello fare le cose "bene", veloce e sporco spesso è utile.
Odrade,

3
Presto! ma: perché sporco?
Raiserle,

2
Questo clone profondo ed è veloce e facile. Attento ad altri suggerimenti in questa pagina. Ne ho provati diversi e non clonano in profondità.
Randall,

2
L'unico aspetto negativo, se puoi chiamarlo così, è che le tue classi devono essere contrassegnate come serializzabili perché questo funzioni.
Tuukka Haapaniemi

30

Per clonare un elenco basta chiamare .ToList (). Questo crea una copia superficiale.

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

3
La soluzione più semplice in
assoluto

29
Un piccolo avvertimento è una copia superficiale ... Ciò creerà due oggetti elenco, ma gli oggetti all'interno saranno gli stessi. Cioè cambiando una proprietà cambierà lo stesso oggetto / proprietà nell'elenco originale.
Mark G

22

Dopo una leggera modifica puoi anche clonare:

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

Non dimenticare che la T dovrebbe essere serializzabile, altrimenti otterrai System.Runtime.Serialization.SerializationException.
Bence Végert,

Buona risposta. Un suggerimento: è possibile aggiungere if (!obj.GetType().IsSerializable) return default(T);come prima istruzione che impedisce l'eccezione. E se lo cambi in un metodo di estensione, potresti persino usare l'operatore Elvis come var b = a?.DeepClone();(dato var a = new List<string>() { "a", "b" }; ad esempio).
Matt,

15

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

List<T> myList = ...;
List<T> cloneOfMyList = new List<T>(myList);

Modifiche myListcome l'inserimento o la rimozione non influiranno cloneOfMyListe viceversa.

Gli oggetti reali contenuti nelle due liste sono comunque gli stessi.


Sono d'accordo con user49126, sto vedendo che è una copia superficiale e le modifiche apportate a un elenco si riflettono nell'altro elenco.
Seidleroni,

1
@Seidleroni, ti sbagli. Le modifiche apportate all'elenco vengono modificate sull'altro elenco, mentre le modifiche all'elenco stesso non lo sono.
Wellington Zanelli,

Questa è una copia superficiale.
Elliot Chen,

Come è una copia superficiale?
mko,

2
@WellingtonZanelli Ho appena confermato che la rimozione di un elemento da myList lo rimuove anche da cloneOfMyList.
Nick Gallimore,

13

Utilizzare AutoMapper (o qualsiasi libreria di mapping che si preferisce) per clonare è semplice e molto gestibile.

Definisci la tua mappatura:

Mapper.CreateMap<YourType, YourType>();

Fai la magia:

YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);

13

Se ti interessa solo i tipi di valore ...

E conosci il tipo:

List<int> newList = new List<int>(oldList);

Se non conosci il tipo prima, avrai bisogno di una funzione di supporto:

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

Il giusto:

List<string> myNewList = Clone(myOldList);

15
Questo non clona gli elementi.
Jeff Yates,

10

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

List<T> newList = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(listToCopy))

Probabilmente non è il modo più efficiente per farlo, ma a meno che non lo faccia 100s su 1000s di volte potresti non notare nemmeno la differenza di velocità.


4
Non si tratta della differenza di velocità, si tratta della leggibilità. Se venissi a questa riga di codice, darei uno schiaffo alla testa e mi chiedevo perché avessero introdotto una libreria di terze parti per serializzare e quindi deserializzare un oggetto che non avrei idea del perché stesse accadendo. Inoltre, questo non funzionerebbe per un elenco di modelli con oggetti che hanno una struttura circolare.
Jonathon Cwik,

1
Questo codice ha funzionato in modo eccellente per me per la clonazione profonda. L'app sta migrando il documento boilerplate da Dev a QA in Prod. Ogni oggetto è un pacchetto di più oggetti modello di documento e ogni documento a sua volta è composto da un elenco di oggetti paragrafo. Questo codice mi consente di serializzare gli oggetti "source" .NET e di deserializzarli immediatamente in nuovi oggetti "target", che vengono quindi salvati in un database SQL in un ambiente diverso. Dopo tonnellate di ricerche, ho trovato molte cose, molte delle quali erano troppo ingombranti, e ho deciso di provarlo. Questo approccio breve e flessibile era "giusto"!
Developer63

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

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

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

3

Io e il mio amico Gregor Martinovic abbiamo trovato questa semplice soluzione usando un serializzatore JavaScript. Non è necessario contrassegnare le classi come serializzabili e nei nostri test utilizzando Newtonsoft JsonSerializer anche più velocemente rispetto all'utilizzo di BinaryFormatter. Con metodi di estensione utilizzabili su ogni oggetto.

Opzione standard .NET JavascriptSerializer:

public static T DeepCopy<T>(this T value)
{
    JavaScriptSerializer js = new JavaScriptSerializer();

    string json = js.Serialize(value);

    return js.Deserialize<T>(json);
}

Opzione più veloce usando Newtonsoft JSON :

public static T DeepCopy<T>(this T value)
{
    string json = JsonConvert.SerializeObject(value);

    return JsonConvert.DeserializeObject<T>(json);
}

2
I membri privati ​​non vengono clonati utilizzando il metodo JSON. stackoverflow.com/a/78612/885627
himanshupareek66

3
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();

3

Sarò fortunato se qualcuno leggerà mai questo ... ma per non restituire un elenco di oggetti tipo nei miei metodi Clone, ho creato un'interfaccia:

public interface IMyCloneable<T>
{
    T Clone();
}

Quindi ho specificato l'estensione:

public static List<T> Clone<T>(this List<T> listToClone) where T : IMyCloneable<T>
{
    return listToClone.Select(item => (T)item.Clone()).ToList();
}

Ed ecco un'implementazione dell'interfaccia nel mio software di marcatura A / V. Volevo che il mio metodo Clone () restituisse un elenco di VidMark (mentre l'interfaccia ICloneable voleva che il mio metodo restituisse un elenco di oggetti):

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

E infine, l'uso dell'estensione all'interno di una classe:

private List<VidMark> _VidMarks;
private List<VidMark> _UndoVidMarks;

//Other methods instantiate and fill the lists

private void SetUndoVidMarks()
{
    _UndoVidMarks = _VidMarks.Clone();
}

Qualcuno piace? Qualche miglioramento?


2

È inoltre possibile semplicemente convertire l'elenco in un array utilizzando ToArray, quindi clonare l'array utilizzando Array.Clone(...). A seconda delle esigenze, i metodi inclusi nella classe Array potrebbero soddisfare le tue esigenze.


Questo non funziona; modifica dei valori nella matrice clonata STILL modifica i valori nell'elenco originale.
Bernoulli Lizard

puoi usare var clonedList = ListOfStrings.ConvertAll (p => p); come indicato da @IbrarMumtaz .... Funziona in modo efficace ... Le modifiche a un elenco vengono mantenute su se stesse e non si riflettono in un altro
zainul

2

È possibile utilizzare il metodo di estensione:

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

}

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

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

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


2

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

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

1

Ho realizzato per me alcune estensioni che convertono ICollection di articoli che non implementano IClonable

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

sembra che alcune raccolte (ad esempio SelectedItems di DataGrid a Silverlight) saltino l'implementazione di CopyTo, il che è un problema con questo approccio
George Birbilis,


1

L'uso di un cast può essere utile, in questo caso, per una copia superficiale:

IList CloneList(IList list)
{
    IList result;
    result = (IList)Activator.CreateInstance(list.GetType());
    foreach (object item in list) result.Add(item);
    return result;
}

applicato all'elenco generico:

List<T> Clone<T>(List<T> argument) => (List<T>)CloneList(argument);

1

Per una copia approfondita, ICloneable è la soluzione corretta, ma ecco un approccio simile a ICloneable usando il costruttore invece dell'interfaccia 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();

avrai bisogno della seguente libreria in cui fai la copia

using System.Linq

puoi anche usare un ciclo for invece di System.Linq, ma Linq lo rende conciso e pulito. Allo stesso modo potresti fare come suggerito da altre risposte e creare metodi di estensione, ecc., Ma nulla di tutto ciò è necessario.


Si chiama "costruttore di copie". È un approccio soggetto a errori, ogni volta che aggiungi un nuovo campo a Student, devi ricordarti di aggiungerlo al costruttore della copia. L'idea principale dietro "clone" è quella di evitare quel problema.
Kenno,

2
Anche con ICloneable, devi avere un metodo "Clone" nella tua classe. A meno che tu non usi la riflessione (che potresti usare anche nell'approccio sopra), quel metodo Clone sembrerà molto simile all'approccio del costruttore della copia sopra e soffrirà dello stesso problema di dover aggiornare per i campi nuovi / modificati. Ma sta dicendo "La classe deve essere aggiornata quando cambiano i campi della classe". Certo che sì;)
ztorstri il

0

Il codice seguente dovrebbe essere trasferito in un elenco con modifiche minime.

Fondamentalmente funziona inserendo un nuovo numero casuale da un intervallo maggiore con ogni ciclo successivo. Se esistono già numeri uguali o superiori, sposta i numeri casuali di uno in modo da trasferirli 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;
}

0

Un'altra cosa: potresti usare la riflessione. Se lo memorizzi correttamente nella cache, 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<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;
    }

L'ho misurato in modo semplice, usando la classe 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 oggetto interno PersonInstance - 16.4, PersonInstance = null - 5.6

CopyFactory è solo la mia classe di test in cui ho dozzine di test incluso l'uso dell'espressione. Potresti implementarlo in un'altra forma in un'estensione o altro. Non dimenticare la memorizzazione nella cache.

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

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

Di recente ho testato il serializzatore Buffer di protocollo con la funzione DeepClone pronta all'uso. Vince con 4,2 secondi su un milione di oggetti semplici, ma quando si tratta di oggetti interni, vince con il risultato 7,4 secondi.

Serializer.DeepClone(personList);

SOMMARIO: Se non hai accesso alle lezioni, questo ti aiuterà. Altrimenti dipende dal conteggio degli oggetti. Penso che potresti usare la riflessione fino a 10.000 oggetti (forse un po 'meno), ma per di più il serializzatore Protocollo Buffer funzionerà meglio.


0

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

È possibile creare una classe di estensione:

using Newtonsoft.Json;

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

Per clonare e obiettare:

obj clonedObj = originalObj.jsonCloneObject;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.