Como clonar uma lista genérica em c #?

593

Eu tenho uma lista genérica de objetos em C # e desejo clonar a lista. Os itens da lista são clonáveis, mas não parece haver uma opção a fazer list.Clone().

Existe uma maneira fácil de contornar isso?

Fiona
fonte
44
Você deve dizer se você está procurando uma cópia profunda ou uma cópia superficial
orip
10
O que são cópias profundas e rasas?
Coronel Panic
3
@orip Por clone()definição, não é uma cópia profunda? Em C #, você pode passar ponteiros facilmente com =, pensei.
22412 Chris
13
@Chris uma cópia superficial copia um nível mais profundo do que a cópia do ponteiro. Por exemplo, uma cópia superficial de uma lista terá os mesmos elementos, mas será uma lista diferente.
orip 18/12/12

Respostas:

385

Você pode usar um método de extensão.

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
fonte
71
Eu acho que List.ConvertAll pode fazer isso em um tempo mais rápido, pois pode pré-alocar toda a matriz para a lista, em vez de precisar redimensionar o tempo todo.
MichaelGG
2
@ MichaelGG, e se você não quiser converter, mas apenas clonar / duplicar os itens da lista? Isso funcionaria? || var clonedList = ListOfStrings.ConvertAll (p => p);
precisa saber é o seguinte
29
@IbrarMumtaz: É o mesmo que var clonedList = new List <string> (ListOfStrings);
Brandon Arnold
4
Ótima solução! A propósito, eu prefiro List estático público <T> CLone <T> ... É mais útil em casos como este, porque não é necessário converter mais: List <MyType> cloned = listToClone.Clone ();
Plutoz 15/05
2
esta é a clonagem profunda
George Birbilis
513

Se seus elementos são tipos de valor, você pode simplesmente fazer:

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

No entanto, se eles são tipos de referência e você deseja uma cópia profunda (supondo que seus elementos sejam implementados corretamente ICloneable), você pode fazer algo assim:

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

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

Obviamente, substitua ICloneableos genéricos acima e faça a conversão por qualquer que seja o tipo de elemento que implemente ICloneable.

Se o seu tipo de elemento não suporta, ICloneablemas possui um construtor de cópias, você pode fazer isso:

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

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

Pessoalmente, eu evitaria ICloneablepor causa da necessidade de garantir uma cópia profunda de todos os membros. Em vez disso, sugiro que o construtor de cópias ou um método de fábrica YourType.CopyFrom(YourType itemToCopy)retorne uma nova instância de YourType.

Qualquer uma dessas opções pode ser agrupada por um método (extensão ou não).

Jeff Yates
fonte
1
Eu acho que List <T> .ConvertAll pode parecer melhor do que criar uma nova lista e fazer um foreach + add.
MichaelGG
2
@ Dimitri: Não, isso não é verdade. O problema é que, quando ICloneablefoi definida, a definição nunca declarou se o clone era profundo ou superficial, portanto, você não pode determinar que tipo de operação de Clone será realizada quando um objeto a implementar. Isso significa que, se você quiser fazer um clone profundo List<T>, precisará fazê-lo sem ICloneableter certeza de que é uma cópia profunda.
Jeff Yates
5
Por que não usar o método AddRange? ( newList.AddRange(oldList.Select(i => i.Clone())or newList.AddRange(oldList.Select(i => new YourType(i))
phoog
5
@ Phoho: Eu acho que é um pouco menos legível / compreensível ao digitalizar o código, isso é tudo. A legibilidade vence para mim.
Jeff Yates
1
@JeffYates: Uma ruga insuficientemente considerada é que as coisas geralmente só precisam ser copiadas se existir algum caminho de execução que as modifique. É muito comum que tipos imutáveis ​​mantenham uma referência a uma instância do tipo mutável, mas nunca a exponha a algo que a modifique. A cópia desnecessária de coisas que nunca mudarão às vezes pode ser uma grande perda de desempenho, aumentando o uso da memória em ordens de magnitude.
Supercat 23/09/13
84

Para uma cópia superficial, você pode usar o método GetRange da classe List genérica.

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

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

Citado de: Generics Recipes

Anthony Potts
fonte
43
Você também pode conseguir isso usando o contratador da Lista <T> para especificar uma Lista <T> da qual copiar. por exemplo, var shallowClonedList = new List <MyObject> (originalList);
Arkiliknam 16/02/2012
9
Eu costumo usar List<int> newList = oldList.ToList(). O mesmo efeito. No entanto, a solução da Arkiliknam é melhor para facilitar a leitura na minha opinião.
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;
}

Essa é uma maneira de fazer isso com C # e .NET 2.0. Seu objeto precisa ser [Serializable()]. O objetivo é perder todas as referências e criar novas.

Patrick Desjardins
fonte
11
+1 - gosto desta resposta - é rápida, suja, desagradável e muito eficaz. Eu usei no silverlight e usei o DataContractSerializer, pois o BinarySerializer não estava disponível. Quem precisa escrever páginas de código de clonagem de objetos quando você pode fazer isso? :)
slugster
3
Eu gosto disso. Embora seja bom fazer as coisas "direito", rápido e sujo geralmente são úteis.
Odrade
3
Rápido! mas: por que sujo?
raiserle
2
Isso clona profundamente e é rápido e fácil. Cuidado com outras sugestões nesta página. Eu tentei vários e eles não clonam profundamente.
RandallTo
2
O único aspecto negativo, se você pode chamar assim, é que suas aulas precisam ser marcadas como serializáveis ​​para que isso funcione.
Tuukka Haapaniemi
30

Para clonar uma lista, basta chamar .ToList (). Isso cria uma cópia 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
fonte
3
Solução mais simples de longe
curveorzos 14/11
29
Um pequeno aviso de que é uma cópia superficial ... Isso criará dois objetos de lista, mas os objetos dentro serão os mesmos. Ou seja, alterar uma propriedade alterará o mesmo objeto / propriedade na lista original.
Mark G
22

Após uma ligeira modificação, você também pode 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
fonte
Não esqueça que o T deve ser serializável; caso contrário, você obtém System.Runtime.Serialization.SerializationException.
Bence Végert
Boa resposta. Uma dica: você pode adicionar if (!obj.GetType().IsSerializable) return default(T);como a primeira instrução que impede a exceção. E se você alterá-lo para um método de extensão, você pode até usar o operador Elvis como var b = a?.DeepClone();(dado var a = new List<string>() { "a", "b" }; por exemplo).
Matt
15

A menos que você precise de um clone real de cada objeto dentro do seu List<T>, a melhor maneira de clonar uma lista é criar uma nova lista com a lista antiga como parâmetro de coleção.

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

Alterações em myListinserir ou remover não afetarão cloneOfMyListe vice-versa.

Os objetos reais contidos nas duas listas ainda são os mesmos.

Jader Feijo
fonte
Concordo com user49126, estou vendo que é uma cópia superficial e as alterações feitas em uma lista são refletidas na outra lista.
Seidleroni 19/11
1
@Seidleroni, você está errado. As alterações feitas nos itens da lista são afetadas na outra lista, as alterações na própria lista não são.
precisa
Esta é uma cópia superficial.
Elliot Chen
Como isso é uma cópia superficial?
Mk
2
@WellingtonZanelli Acabei de confirmar que a remoção de um elemento do myList também o remove do cloneOfMyList.
precisa
13

Use o AutoMapper (ou qualquer outra biblioteca de mapeamento que você preferir) para clonar é simples e muito sustentável.

Defina seu mapeamento:

Mapper.CreateMap<YourType, YourType>();

Faça a mágica:

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

Se você se importa apenas com os tipos de valor ...

E você sabe o tipo:

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

Se você não conhece o tipo antes, precisará de uma função auxiliar:

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

O justo:

List<string> myNewList = Clone(myOldList);
James Curran
fonte
15
Isso não clona os elementos.
Jeff Yates
10

Se você já fez referência ao Newtonsoft.Json no seu projeto e seus objetos são serializáveis, você sempre pode usar:

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

Possivelmente não é a maneira mais eficiente de fazê-lo, mas, a menos que você esteja fazendo isso centenas de milhares de vezes, você nem perceberá a diferença de velocidade.

ProfNimrod
fonte
4
Não é sobre a diferença de velocidade, é sobre a legibilidade. Se eu chegasse a essa linha de código, daria um tapa na cabeça e me perguntaria por que eles introduziram uma biblioteca de terceiros para serializar e desserializar um objeto que eu não teria idéia do por que está acontecendo. Além disso, isso não funcionaria para uma lista de modelos com objetos que possuem uma estrutura circular.
Jonathon Cwik
1
Esse código funcionou excelentemente para mim na clonagem profunda. O aplicativo está migrando o clichê de documentos do Dev para o QA para o Prod. Cada objeto é um pacote de vários objetos de modelo de documento e cada documento, por sua vez, é composto de uma lista de objetos de parágrafo. Esse código me permite serializar os objetos "origem" do .NET e desserializá-los imediatamente para novos objetos "destino", que são salvos em um banco de dados SQL em um ambiente diferente. Após toneladas de pesquisa, encontrei muitas coisas, muitas das quais eram muito pesadas, e decidi tentar isso. Essa abordagem curta e flexível foi "perfeita"!
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;
}
pratik
fonte
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;
  }
}
Pedro
fonte
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
fonte
3

Meu amigo Gregor Martinovic e eu criamos essa solução fácil usando um serializador de JavaScript. Não há necessidade de sinalizar classes como Serializable e, em nossos testes, o Newtonsoft JsonSerializer é ainda mais rápido do que o BinaryFormatter. Com métodos de extensão utilizáveis ​​em todos os objetos.

Opção .NET JavascriptSerializer padrão:

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

    string json = js.Serialize(value);

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

Opção mais rápida usando o Newtonsoft JSON :

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

    return JsonConvert.DeserializeObject<T>(json);
}
FH
fonte
2
Membros privados não são clonados usando o método JSON. Você está em
Página
3
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();
Steve
fonte
3

Serei sortudo se alguém ler isso ... mas, para não retornar uma lista do objeto de tipo nos meus métodos Clone, criei uma interface:

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

Então eu especifiquei a extensão:

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

E aqui está uma implementação da interface no meu software de marcação A / V. Eu queria que meu método Clone () retornasse uma lista do VidMark (enquanto a interface ICloneable desejava que meu método retornasse uma 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();
    }
}

E, finalmente, o uso da extensão dentro de uma classe:

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

//Other methods instantiate and fill the lists

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

Alguém gosta disso? Alguma melhoria?

John Kurtz
fonte
2

Você também pode simplesmente converter a lista em uma matriz usando ToArraye depois clonar a matriz usando Array.Clone(...). Dependendo das suas necessidades, os métodos incluídos na classe Array podem atender às suas necessidades.

JHaps
fonte
Isso não funciona; alterações nos valores na matriz clonada AINDA alteram os valores na lista original.
Bernoulli Lizard
você pode usar var clonedList = ListOfStrings.ConvertAll (p => p); como dado por @IbrarMumtaz .... funciona de forma eficaz ... Mudanças a uma lista são mantidos para si e não para não refletir em outro
Zainul
2

Você pode usar o método de extensão:

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

}

Você pode clonar todos os objetos usando seus membros do tipo de valor, por exemplo, considere esta 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 você fizer alguma alteração na cópia (ou clone), isso não afetará o objeto original.

Furkan Katı
fonte
2

Se você precisar de uma lista clonada com a mesma capacidade, tente:

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

Criei por mim próprio uma extensão que converte ICollection de itens que não implementam o 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
fonte
Parece que algumas coleções (por exemplo SelectedItems de DataGrid no Silverlight) ignorar a implementação de CopyTo que é um problema com esta abordagem
George Birbilis
1

Eu uso o automapper para copiar um objeto. Acabei de configurar um mapeamento que mapeia um objeto para si mesmo. Você pode quebrar esta operação da maneira que desejar.

http://automapper.codeplex.com/

Dan H
fonte
1

Usar um elenco pode ser útil, neste caso, para uma cópia superficial:

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

aplicado à lista genérica:

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

Para uma cópia profunda, o ICloneable é a solução correta, mas aqui está uma abordagem semelhante ao ICloneable usando o construtor em vez da interface do 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();

você precisará da seguinte biblioteca onde você faz a cópia

using System.Linq

você também pode usar um loop for em vez do System.Linq, mas o Linq o torna conciso e limpo. Da mesma forma, você poderia fazer o que outras respostas sugeriram e criar métodos de extensão etc., mas nada disso é necessário.

ztorstri
fonte
Isso é chamado de "construtor de cópias". É uma abordagem propensa a erros, sempre que você adiciona um novo campo ao Student, lembre-se de adicioná-lo ao construtor de cópias. A principal idéia por trás do "clone" é evitar esse problema.
Kenno
2
Mesmo com o ICloneable, você precisa ter um método "Clone" em sua classe. A menos que você use a reflexão (que você também pode usar na abordagem acima), esse método Clone será realmente semelhante à abordagem do construtor de cópias acima e sofrerá com o mesmo problema de ter que atualizar para campos novos / alterados. Mas isso está dizendo "A classe precisa ser atualizada quando os campos da classe mudarem". Claro que sim;)
ztorstri
0

O código a seguir deve ser transferido para uma lista com alterações mínimas.

Basicamente, ele funciona inserindo um novo número aleatório de um intervalo maior a cada loop sucessivo. Se já existem números iguais ou superiores a ele, mude esses números aleatórios um para cima para que eles sejam transferidos para o novo intervalo maior de índices aleatórios.

// 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
fonte
0

Outra coisa: você pode usar a reflexão. Se você armazenar isso em cache corretamente, clonará 1.000.000 de objetos em 5,6 segundos (infelizmente, 16,4 segundos com 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;
    }

Eu a medi de maneira simples, usando a 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;

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

CopyFactory é apenas a minha classe de teste, onde tenho dezenas de testes, incluindo o uso de expressão. Você pode implementar isso de outra forma em uma extensão ou qualquer outra coisa. Não se esqueça do cache.

Ainda não testei a serialização, mas duvido de uma melhoria com um milhão de classes. Vou tentar algo rápido protobuf / newton.

PS: para simplificar a leitura, usei apenas propriedades automáticas aqui. Eu poderia atualizar com o FieldInfo ou você deve implementá-lo facilmente.

Recentemente, testei o serializador de buffers de protocolo com a função DeepClone pronta para uso. Ele vence com 4,2 segundos em um milhão de objetos simples, mas quando se trata de objetos internos, vence com o resultado 7,4 segundos.

Serializer.DeepClone(personList);

RESUMO: Se você não tiver acesso às aulas, isso ajudará. Caso contrário, isso depende da contagem dos objetos. Eu acho que você pode usar reflexão de até 10.000 objetos (talvez um pouco menos), mas, para mais do que isso, o serializador de buffers de protocolo terá um desempenho melhor.

Roma Borodov
fonte
0

Existe uma maneira simples de clonar objetos em C # usando um serializador e desserializador JSON.

Você pode criar uma classe de extensão:

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 e objetar:

obj clonedObj = originalObj.jsonCloneObject;
Albert arnau
fonte