Pergunta

Eu tenho uma lista genérica de objetos em C # e deseja clonar a lista. Os itens dentro da lista são cloneable, mas não parece ser uma opção para fazer list.Clone().

Existe uma maneira fácil de contornar isso?

Foi útil?

Solução

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

Outras dicas

Se os seus elementos são tipos de valor, então você pode apenas fazer:

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

No entanto, se eles são tipos de referência e você quer uma cópia profunda (assumindo seus elementos implementar adequadamente ICloneable), você poderia fazer algo parecido com isto:

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

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

Obviamente, substituir ICloneable nos genéricos acima e fundido com o que o seu tipo de elemento é que implementos ICloneable.

Se o seu tipo de elemento não suporta ICloneable mas tem uma cópia-construtor, você poderia fazer isso em vez disso:

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 ICloneable por causa da necessidade de garantir uma cópia profunda de todos os membros. Em vez disso, eu sugiro a cópia-construtor ou um método de fábrica como YourType.CopyFrom(YourType itemToCopy) que retorna uma nova instância de YourType.

Qualquer uma dessas opções poderia ser envolvido por um método (extensão ou de outra forma).

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 é uma maneira de fazê-lo com C # e .NET 2.0. Seu objeto requer para ser [Serializable()]. O objetivo é perder todas as referências e construir novos.

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

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

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

Citado de: Genéricos Receitas

Depois de uma ligeira modificação você também pode clone: ??

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

Para clonar uma lista apenas chamar 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<int>() { 3, 4 };
> var y = x.ToList();
> x.Add(5)
> x
List<int>(3) { 3, 4, 5 }
> y
List<int>(2) { 3, 4 }
> 

A menos que você precisa de um clone real de cada objeto dentro de sua List<T>, a melhor maneira para clonar uma lista é a criação de uma nova lista com a lista antiga quanto o parâmetro coleção.

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

Alterações para myList tais como inserir ou remover não irá afectar cloneOfMyList e vice-versa.

Os objetos reais as duas listas contêm ainda são os mesmos no entanto.

Use AutoMapper (ou qualquer mapeamento lib você preferir) para clone é simples e de fácil manutenção muito.

Defina seu mapeamento:

Mapper.CreateMap<YourType, YourType>();

fazer a mágica:

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

Se você só se preocupam com os tipos de valor ...

E você sabe o tipo:

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

Se você não sabe o tipo antes, você vai 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);

Se você já referenciado Newtonsoft.Json em seu projeto e seus objetos são serializeable você poderia sempre usar:

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

Possivelmente não a forma mais eficiente de fazê-lo, mas a menos que você está fazendo isso 100s de 1000s de vezes que você pode nem notar a diferença de velocidade.

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

Você também pode simplesmente converter a lista para uma matriz usando ToArray, e depois clonar a matriz usando Array.Clone(...). Dependendo de suas necessidades, os métodos incluídos na classe Array poderia atender às suas necessidades.

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 tipo de valor para 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 qualquer alteração na cópia (ou clone) não vai afetar o objeto original

Se precisar de uma lista clonados com a mesma capacidade, você pode tentar o seguinte:

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

Meu amigo Gregor Martinovic e eu vim com essa solução fácil usando um Serializer JavaScript. Não há necessidade de aulas marcar como Serializable e em nossos testes usando o Newtonsoft JsonSerializer ainda mais rápido do que usando BinaryFormatter. Com extensão de métodos utilizáveis ??em cada objeto.

opção padrão .NET JavaScriptSerializer:

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 Newtonsoft JSON :

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

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

Eu fiz para o meu próprio alguma extensão que converte ICollection de itens que não implementar 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();
    }
}

Eu uso AutoMapper para copiar um objeto. Eu apenas configurar um mapeamento que mapeia um objeto para si mesmo. Você pode quebrar esta operação qualquer jeito que você gosta.

http://automapper.codeplex.com/

Usando um elenco pode ser útil, neste caso, por 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;
}

aplicada a lista genérica:

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

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

Basicamente ele funciona através da inserção de um novo número aleatório a partir de uma gama maior com cada ciclo sucessivo. Se existirem números já que são iguais ou maiores do que isso, mudar esses números aleatórios-se um modo que pode transferir para a nova maior gama 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;
}

Outra coisa: você pode usar a reflexão. Se você armazenar em cache isso corretamente, então ele vai clonar 1.000.000 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 medi-lo de uma forma 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 PersonInstance objeto interno - 16,4, PersonInstance = null - 5.6

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

Eu não testei serialização ainda, mas eu duvido numa melhoria com um milhão de classes. Vou tentar algo rápido protobuf / newton.

P.S .: por causa da leitura simplicidade, eu só usei auto-propriedade aqui. Eu poderia atualizar com FieldInfo, ou você deve facilmente implementar isso por conta própria.

Eu recentemente testou o Protocol Buffers serializer com a função DeepClone fora da caixa. Ele vence com 4,2 segundos em um milhão de objetos simples, mas quando se trata de objetos internos, ganha com o resultado de 7,4 segundos.

Serializer.DeepClone(personList);

RESUMO: Se você não tem acesso às aulas, então isso vai ajudar. Caso contrário, depende da contagem dos objetos. Eu acho que você poderia usar a reflexão até 10.000 objetos (talvez um pouco menos), mas mais do que isso o serializador Protocol Buffers terá um desempenho melhor.

Há uma maneira simples de objetos de clone em C # usando um serializador JSON e desserializador.

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 clone e objeto:

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

Eu vou ter sorte se ninguém nunca lê este ... mas, a fim de não retornar uma lista de tipo de objeto em meus métodos Clone, eu criei uma interface:

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

Então eu especificado 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 é uma implementação da interface no meu software marcação A / V. Eu queria ter o meu método Clone () retornar uma lista de Vidmark (enquanto a interface ICloneable queria o meu método para retornar uma lista de objeto):

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 como ele? Todas as melhorias?

Para obter uma cópia profunda, ICloneable é a solução correta, mas aqui está uma abordagem semelhante para ICloneable usando o construtor em vez da interface 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ê vai precisar do seguinte biblioteca onde você faz a cópia

using System.Linq

Você também pode usar um loop em vez de System.Linq, mas Linq faz com que seja conciso e limpo. Da mesma forma que você poderia fazer como outras respostas sugeriram e métodos de extensão marca, etc., mas nada disso é necessário.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top