¿Cómo clono una lista genérica en C #?

593

Tengo una lista genérica de objetos en C # y deseo clonar la lista. Los elementos dentro de la lista son clonables, pero no parece haber una opción para hacerlo list.Clone().

¿Hay alguna manera fácil de evitar esto?

Fiona
fuente
44
Debería decir si está buscando una copia profunda o una copia superficial
orip
10
¿Qué son las copias profundas y superficiales?
Coronel Panic
55
@ColonelPanic es.wikipedia.org/wiki/Object_copy#Shallow_copy
Nathan Koop
3
@orip ¿No es, clone()por definición, una copia profunda? En C # puedes pasar fácilmente los punteros con =, pensé.
Chris
13
@ Chris una copia superficial copia un nivel más profundo que la copia del puntero. Por ejemplo, una copia superficial de una lista tendrá los mismos elementos, pero será una lista diferente.
orip

Respuestas:

385

Puedes usar un método de extensión.

static class Extensions
{
    public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
    {
        return listToClone.Select(item => (T)item.Clone()).ToList();
    }
}
ajm
fuente
71
Creo que List.ConvertAll podría hacer esto en un tiempo más rápido, ya que puede preasignar toda la matriz para la lista, en lugar de tener que cambiar el tamaño todo el tiempo.
MichaelGG
2
@MichaelGG, ¿qué pasa si no desea convertir sino simplemente clonar / duplicar los elementos de la lista? ¿Funcionaría esto? || var clonedList = ListOfStrings.ConvertAll (p => p);
IbrarMumtaz
29
@IbrarMumtaz: Eso es lo mismo que var clonedList = new List <string> (ListOfStrings);
Brandon Arnold
44
Buena solución! Por cierto, prefiero la lista estática pública <T> CLone <T> ... Es más útil en casos como este, porque no se necesita más conversión: List <MyType> cloned = listToClone.Clone ();
Plutón
2
esto es una clonación profunda
George Birbilis
513

Si sus elementos son tipos de valor, puede hacer lo siguiente:

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

Sin embargo, si son tipos de referencia y desea una copia profunda (suponiendo que sus elementos se implementen correctamente ICloneable), podría hacer algo como esto:

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

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

Obviamente, reemplace ICloneableen los genéricos anteriores y eche cualquier tipo de elemento que implementeICloneable .

Si su tipo de elemento no es compatible ICloneablepero tiene un constructor de copia, puede hacer esto en su lugar:

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

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

Personalmente, lo evitaría ICloneablepor la necesidad de garantizar una copia profunda de todos los miembros. En cambio, sugeriría que el constructor de copias o un método de fábrica como YourType.CopyFrom(YourType itemToCopy)ese devuelva una nueva instancia deYourType .

Cualquiera de estas opciones podría ajustarse mediante un método (extensión u otro).

Jeff Yates
fuente
1
Creo que List <T> .ConvertAll podría verse mejor que crear una nueva lista y hacer un foreach + add.
MichaelGG
2
@Dimitri: No, eso no es cierto. El problema es que, cuando ICloneablese definió, la definición nunca indicó si el clon era profundo o poco profundo, por lo que no puede determinar qué tipo de operación Clone se realizará cuando un objeto lo implemente. Esto significa que si desea hacer un clon profundo List<T>, tendrá que hacerlo sin ICloneableasegurarse de que sea una copia profunda.
Jeff Yates
55
¿Por qué no usar el método AddRange? ( newList.AddRange(oldList.Select(i => i.Clone())o newList.AddRange(oldList.Select(i => new YourType(i))
phoog
55
@phoog: Creo que es un poco menos legible / comprensible al escanear el código, eso es todo. La legibilidad gana para mí.
Jeff Yates
1
@JeffYates: Una arruga insuficientemente considerada es que, en general, las cosas solo necesitan copiarse si existe alguna ruta de ejecución que las mute. Es muy común que los tipos inmutables contengan una referencia a una instancia de tipo mutable, pero nunca exponga esa instancia a nada que la mute. Copiar innecesariamente cosas que nunca van a cambiar a veces puede ser una pérdida de rendimiento importante , aumentando el uso de memoria por órdenes de magnitud.
supercat
84

Para una copia superficial, puede utilizar el método GetRange de la clase genérica List.

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

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

Citado de: Recetas genéricas

Anthony Potts
fuente
43
También puede lograr esto utilizando el contructor de List <T> para especificar una Lista <T> desde la cual copiar. por ejemplo, var shallowClonedList = new List <MyObject> (originalList);
Arkiliknam
99
Yo uso a menudo List<int> newList = oldList.ToList(). Mismo efecto. Sin embargo, la solución de Arkiliknam es la mejor para la legibilidad en mi opinión.
Dan Bechard el
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;
}

Esta es una forma de hacerlo con C # y .NET 2.0. Tu objeto requiere ser [Serializable()]. El objetivo es perder todas las referencias y construir nuevas.

Patrick Desjardins
fuente
11
+1: me gusta esta respuesta: es rápida, sucia, desagradable y muy efectiva. Usé en Silverlight, y usé DataContractSerializer ya que BinarySerializer no estaba disponible. ¿Quién necesita escribir páginas de código de clonación de objetos cuando puede hacer esto? :)
slugster
3
Me gusta esto. Si bien es bueno hacer las cosas "bien", lo rápido y lo sucio suele ser útil.
Odrade
3
¡Rápido! pero: ¿por qué sucio?
Raiserle
2
Este clones profundos y es rápido y fácil. Cuidado con otras sugerencias en esta página. Intenté varios y no clonan profundamente.
Randall Hasta
2
El único aspecto negativo, si puede llamarlo así, es que sus clases deben marcarse Serializable para que esto funcione.
Tuukka Haapaniemi
30

Para clonar una lista simplemente llame a .ToList (). Esto crea una copia superficial.

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 }
> 
Xavier John
fuente
3
La solución más simple con diferencia
curveorzos
29
Una pequeña advertencia de que esta es una copia superficial ... Esto creará dos objetos de lista, pero los objetos dentro serán los mismos. Es decir, cambiar una propiedad cambiará el mismo objeto / propiedad en la lista original.
Mark G el
22

Después de una ligera modificación, también puede clonar:

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;
}
Ajith
fuente
No olvide que la T debe ser serializable, de lo contrario obtendrá System.Runtime.Serialization.SerializationException.
Bence Végert el
Buena respuesta. Una pista: podría agregar if (!obj.GetType().IsSerializable) return default(T);como la primera instrucción que evita la excepción. Y si lo cambia a un método de extensión, incluso podría usar el operador Elvis como var b = a?.DeepClone();(dado, var a = new List<string>() { "a", "b" }; por ejemplo).
Matt
15

A menos que necesite un clon real de cada objeto dentro de su List<T>, la mejor manera de clonar una lista es crear una nueva lista con la lista anterior como parámetro de colección.

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

Los cambios myListcomo insertar o eliminar no afectarán cloneOfMyListy viceversa.

Sin embargo, los objetos reales que contienen las dos listas siguen siendo los mismos.

Jader Feijo
fuente
Estoy de acuerdo con user49126, veo que es una copia superficial y los cambios realizados en una lista se reflejan en la otra lista.
Seidleroni
1
@Seidleroni, estás equivocado. Los cambios realizados en los itens de la lista se ven afectados en la otra lista, los cambios en la lista en sí no.
Wellington Zanelli
Esta es una copia superficial.
Elliot Chen
¿Cómo es esto una copia superficial?
mko
2
@WellingtonZanelli Acaba de confirmar que eliminar un elemento de myList también lo elimina de cloneOfMyList.
Nick Gallimore
13

Usar AutoMapper (o cualquier lib de mapeo que prefiera) para clonar es simple y muy fácil de mantener.

Defina su mapeo:

Mapper.CreateMap<YourType, YourType>();

Haz la magia

YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);
Derek Liang
fuente
13

Si solo te interesan los tipos de valor ...

Y sabes el tipo:

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

Si no conoce el tipo antes, necesitará una función auxiliar:

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

El justo:

List<string> myNewList = Clone(myOldList);
James Curran
fuente
15
Esto no clona los elementos.
Jeff Yates
10

Si ya ha hecho referencia a Newtonsoft.Json en su proyecto y sus objetos son serializables, siempre puede usar:

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

Posiblemente no sea la forma más eficiente de hacerlo, pero a menos que lo esté haciendo cientos de miles de veces, es posible que ni siquiera note la diferencia de velocidad.

ProfNimrod
fuente
44
No se trata de la diferencia de velocidad, se trata de la legibilidad. Si llego a esta línea de código, me golpearía la cabeza y me preguntaría por qué introdujeron una biblioteca de terceros para serializar y luego deserializar un objeto que no tendría idea de por qué está sucediendo. Además, esto no funcionaría para una lista de modelos con objetos que tienen una estructura circular.
Jonathon Cwik
1
Este código funcionó excelentemente para mí para la clonación profunda. La aplicación está migrando la plantilla de documentos de Dev a QA a Prod. Cada objeto es un paquete de varios objetos de plantilla de documento, y cada documento a su vez se compone de una lista de objetos de párrafo. Este código me permite serializar los objetos "fuente" .NET e inmediatamente deserializarlos a nuevos objetos "destino", que luego se guardan en una base de datos SQL en un entorno diferente. Después de toneladas de investigación, encontré muchas cosas, muchas de las cuales eran demasiado engorrosas, y decidí probar esto. ¡Este enfoque corto y flexible fue "justo"!
Desarrollador63
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;
}
pratik
fuente
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;
  }
}
Peter
fuente
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;
        }
    }
shahrooz.bazrafshan
fuente
3

Mi amigo Gregor Martinovic y yo se nos ocurrió esta solución fácil usando un serializador de JavaScript. No es necesario marcar las clases como serializables y en nuestras pruebas con Newtonsoft JsonSerializer incluso más rápido que con BinaryFormatter. Con métodos de extensión utilizables en cada objeto.

Opción estándar .NET JavascriptSerializer:

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

    string json = js.Serialize(value);

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

Opción más rápida con Newtonsoft JSON :

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

    return JsonConvert.DeserializeObject<T>(json);
}
FH
fuente
2
Los miembros privados no se clonan utilizando el método JSON. stackoverflow.com/a/78612/885627
himanshupareek66
3
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();
Steve
fuente
3

Tendré suerte si alguien lee esto ... pero para no devolver una lista de objetos tipo en mis métodos Clone, creé una interfaz:

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

Luego especifiqué la extensión:

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

Y aquí hay una implementación de la interfaz en mi software de marcado A / V. Quería que mi método Clone () devolviera una lista de VidMark (mientras que la interfaz ICloneable quería que mi método devolviera una lista de objetos):

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

Y finalmente, el uso de la extensión dentro de una clase:

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

//Other methods instantiate and fill the lists

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

A alguien le gusta? ¿Alguna mejora?

John Kurtz
fuente
2

También puede simplemente convertir la lista a una matriz usando ToArray, y luego clonar la matriz usando Array.Clone(...). Dependiendo de sus necesidades, los métodos incluidos en la clase Array podrían satisfacer sus necesidades.

JHaps
fuente
Esto no funciona; cambios en los valores en la matriz clonada TODAVÍA cambian los valores en la lista original.
Bernoulli Lizard
puede usar var clonedList = ListOfStrings.ConvertAll (p => p); según lo dado por @IbrarMumtaz ... Funciona de manera efectiva ... Los cambios en una lista se guardan en sí mismos y no se reflejan en otra
zainul
2

Puedes usar el método de extensión:

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

}

Puede clonar todos los objetos utilizando sus miembros de tipo de valor, por ejemplo, considere esta clase:

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: si realiza algún cambio en la copia (o clonación), no afectará al objeto original.

Furkan Katı
fuente
2

Si necesita una lista clonada con la misma capacidad, puede probar esto:

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

He creado una extensión que convierte ICollection de elementos que no implementan 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();
    }
}
Kamil Budziewski
fuente
Parece que algunas colecciones (p. ej., SelectedItems de DataGrid en Silverlight) omiten la implementación de CopyTo, lo cual es un problema con este enfoque
George Birbilis
1

Yo uso automapper para copiar un objeto. Acabo de configurar una asignación que asigna un objeto a sí mismo. Puede ajustar esta operación de la forma que desee.

http://automapper.codeplex.com/

Dan H
fuente
1

Usar un yeso puede ser útil, en este caso, para una copia superficial:

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

aplicado a la lista genérica:

List<T> Clone<T>(List<T> argument) => (List<T>)CloneList(argument);
Thomas Cerny
fuente
1

Para una copia profunda, ICloneable es la solución correcta, pero aquí hay un enfoque similar a ICloneable utilizando el constructor en lugar de la interfaz 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();

necesitará la siguiente biblioteca donde hace la copia

using System.Linq

También puede usar un bucle for en lugar de System.Linq, pero Linq lo hace conciso y limpio. Del mismo modo, podría hacer lo que otras respuestas han sugerido y hacer métodos de extensión, etc., pero nada de eso es necesario.

ztorstri
fuente
Eso se llama un "constructor de copias". Es un enfoque propenso a errores, cada vez que agrega un nuevo campo a Estudiante, debe recordar agregarlo al constructor de la copia. La idea principal detrás de "clonar" es evitar ese problema.
kenno
2
Incluso con ICloneable, debe tener un método "Clone" en su clase. A menos que use la reflexión (que también podría usar en el enfoque anterior), ese método Clone se verá muy similar al enfoque del constructor de copias anterior y sufrirá el mismo problema de tener que actualizar los campos nuevos / modificados. Pero eso dice "La clase tiene que actualizarse cuando cambian los campos de la clase". Por supuesto que sí;)
ztorstri
0

El siguiente código debe transferirse a una lista con cambios mínimos.

Básicamente funciona insertando un nuevo número aleatorio de un rango mayor con cada ciclo sucesivo. Si ya existen números que son iguales o superiores, cambie esos números aleatorios hacia arriba para que se transfieran al nuevo rango más grande de índices aleatorios.

// 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;
}
Adam Lewis
fuente
0

Otra cosa: podrías usar la reflexión. Si almacena esto correctamente, clonará 1,000,000 de objetos en 5.6 segundos (lamentablemente, 16.4 segundos con objetos internos).

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

Lo medí de una manera simple, usando la clase 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;

RESULTADO: Con objeto interno PersonInstance - 16.4, PersonInstance = null - 5.6

CopyFactory es solo mi clase de prueba donde tengo una docena de pruebas que incluyen el uso de la expresión. Puede implementar esto de otra forma en una extensión o lo que sea. No te olvides del almacenamiento en caché.

Todavía no probé la serialización, pero dudo de una mejora con un millón de clases. Probaré algo rápido protobuf / newton.

PD: en aras de la simplicidad de lectura, solo utilicé la propiedad automática aquí. Podría actualizar con FieldInfo, o debería implementar esto fácilmente por su cuenta.

Recientemente probé el serializador Protocol Buffers con la función DeepClone lista para usar. Gana con 4.2 segundos en un millón de objetos simples, pero cuando se trata de objetos internos, gana con el resultado 7.4 segundos.

Serializer.DeepClone(personList);

RESUMEN: Si no tienes acceso a las clases, esto te ayudará. De lo contrario, depende del recuento de los objetos. Creo que podría usar la reflexión de hasta 10,000 objetos (quizás un poco menos), pero para más de esto el serializador de Protocol Buffers funcionará mejor.

Roma Borodov
fuente
0

Hay una manera simple de clonar objetos en C # usando un serializador y deserializador JSON.

Puede crear una clase de extensión:

using Newtonsoft.Json;

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

Para clonar y objetar:

obj clonedObj = originalObj.jsonCloneObject;
Albert arnau
fuente