Objetos de clonagem profunda

2226

Eu quero fazer algo como:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

E, em seguida, faça alterações no novo objeto que não são refletidas no objeto original.

Como muitas vezes não preciso dessa funcionalidade, quando é necessário, comecei a criar um novo objeto e a copiar cada propriedade individualmente, mas sempre me deixa com a sensação de que há uma maneira melhor ou mais elegante de lidar. a situação.

Como posso clonar ou copiar em profundidade um objeto para que o objeto clonado possa ser modificado sem que nenhuma alteração seja refletida no objeto original?

NakedBrunch
fonte
81
Pode ser útil: "Por que copiar um objeto é uma coisa terrível a se fazer?" agiledeveloper.com/articles/cloning072002.htm
Pedro77
stackoverflow.com/questions/8025890/... Outra solução ...
Felix K.
18
Você deve dar uma olhada no AutoMapper
Daniel Little
3
Sua solução é bem mais complexa, eu me perdi lendo ... hehehe. Estou usando uma interface DeepClone. interface pública IDeepCloneable <T> {T DeepClone (); }
Pedro77
1
@ Pedro77 - Embora, curiosamente, esse artigo acabe dizendo para criar um clonemétodo na classe e, em seguida, chame um construtor interno e privado que seja aprovado this. Portanto, copiar é permitido [sic], mas copiar com cuidado (e o artigo definitivamente vale a pena ler) não é. ; ^)
ruffin

Respostas:

1715

Embora a prática padrão seja implementar a ICloneableinterface (descrita aqui , para que eu não volte a regurgitar), aqui está uma boa copiadora profunda de objetos clone que encontrei no The Code Project há algum tempo e a incorporei em nossas coisas.

Como mencionado em outro lugar, ele exige que seus objetos sejam serializáveis.

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", nameof(source));
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }
}

A idéia é que ele serialize seu objeto e o desserialize para um novo objeto. O benefício é que você não precisa se preocupar em clonar tudo quando um objeto fica muito complexo.

E com o uso de métodos de extensão (também da fonte originalmente referenciada):

Caso você prefira usar os novos métodos de extensão do C # 3.0, altere o método para ter a seguinte assinatura:

public static T Clone<T>(this T source)
{
   //...
}

Agora a chamada do método simplesmente se torna objectBeingCloned.Clone();.

EDIT (10 de janeiro de 2015) Pensei em revisitar isso, para mencionar que recentemente comecei a usar o (Newtonsoft) Json para fazer isso, deve ser mais leve e evita a sobrecarga das tags [Serializable]. ( NB @atconway indicou nos comentários que membros privados não são clonados usando o método JSON)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
johnc
fonte
24
stackoverflow.com/questions/78536/cloning-objects-in-c/... tem uma ligação para o código acima [e referências duas outras implementações, um dos quais é mais apropriado no meu contexto]
Ruben Bartelink
102
A serialização / desserialização envolve uma sobrecarga significativa que não é necessária. Consulte os métodos de interface ICloneable e .MemberWise () em C #.
precisa saber é o seguinte
18
@ David, concedido, mas se os objetos forem leves e o desempenho atingido ao usá-lo não for muito alto para seus requisitos, é uma dica útil. Não o usei intensivamente com grandes quantidades de dados em loop, admito, mas nunca vi uma única preocupação com o desempenho.
procurando
16
@Amir: na verdade, não: typeof(T).IsSerializabletambém é verdadeiro se o tipo foi marcado com o [Serializable]atributo. Não precisa implementar a ISerializableinterface.
precisa saber é o seguinte
11
Só pensei em mencionar que, embora esse método seja útil e que eu já o usei várias vezes, ele não é compatível com o Medium Trust - portanto, tenha cuidado se você estiver escrevendo um código que precisa de compatibilidade. O BinaryFormatter acessa campos particulares e, portanto, não pode funcionar no conjunto de permissões padrão para ambientes de confiança parcial. Você pode tentar outro serializador, mas verifique se o chamador sabe que o clone pode não ser perfeito se o objeto recebido depender de campos particulares.
Alex Norcliffe
298

Eu queria um clonador para objetos muito simples, principalmente de primitivas e listas. Se seu objeto estiver fora da caixa JSON serializável, esse método fará o truque. Isso não requer modificação ou implementação de interfaces na classe clonada, apenas um serializador JSON como o JSON.NET.

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}

Além disso, você pode usar este método de extensão

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonConvert.SerializeObject(source);
        return JsonConvert.DeserializeObject<T>(serialized);
    }
}
craastad
fonte
13
o solutiojn é ainda mais rápido do que a solução BinaryFormatter, .NET serialização Comparação de desempenho
esskar
3
Obrigado por isso. Consegui fazer essencialmente a mesma coisa com o serializador BSON que acompanha o driver MongoDB para C #.
precisa
3
Esta é a melhor maneira para mim, no entanto, eu uso Newtonsoft.Json.JsonConvert, mas é o mesmo
Pierre
1
Para isto funcionar o objecto a necessidades de clones para ser seriável como já mencionado - Isto também significa por exemplo, que ele pode não têm dependências circulares
radomeit
2
Eu acho que essa é a melhor solução, pois a implementação pode ser aplicada na maioria das linguagens de programação.
Mr5
178

O motivo para não usar o ICloneable não é porque ele não possui uma interface genérica. A razão para não usá-lo é porque é vago . Não fica claro se você está recebendo uma cópia superficial ou profunda; isso depende do implementador.

Sim, MemberwiseClonefaz uma cópia superficial, mas o oposto de MemberwiseClonenão é Clone; seria, talvez DeepClone, o que não existe. Quando você usa um objeto por meio de sua interface ICloneable, não pode saber que tipo de clonagem o objeto subjacente executa. (E os comentários XML não deixarão claro, porque você obterá os comentários da interface, e não os do método Clone do objeto.)

O que eu costumo fazer é simplesmente criar um Copymétodo que faça exatamente o que eu quero.

Ryan Lundy
fonte
Não sei por que o ICloneable é considerado vago. Dado um tipo como Dictionary (Of T, U), eu esperaria que o ICloneable.Clone faça qualquer nível de cópia profunda e superficial necessária para tornar o novo dicionário um dicionário independente que contenha os mesmos T's e U's (conteúdo da estrutura, e / ou referências a objetos) como o original. Onde está a ambiguidade? Certamente, um ICloneable (Of T) genérico, que herdou ISelf (Of T), que incluía um método "Self", seria muito melhor, mas não vejo ambiguidade em clonagem profunda versus superficial.
Supercat
31
Seu exemplo ilustra o problema. Suponha que você tenha um dicionário <string, Customer>. O Dicionário clonado deve ter os mesmos objetos do Cliente que o original ou cópias desses objetos do Cliente? Existem casos de uso razoáveis ​​para qualquer um deles. Mas o ICloneable não deixa claro qual deles você receberá. É por isso que não é útil.
Ryan Lundy
@ Kyralessa O artigo do Microsoft MSDN na verdade afirma esse problema de não saber se você está solicitando uma cópia profunda ou superficial.
esmagar
A resposta do duplicado stackoverflow.com/questions/129389/... descreve extensão de cópia, com base em MembershipClone recursiva
Michael Freidgeim
123

Após muita leitura sobre muitas das opções vinculadas aqui e possíveis soluções para esse problema, acredito que todas as opções estão resumidas muito bem no link de Ian P (todas as outras opções são variações daquelas) e a melhor solução é fornecida por O link de Pedro77 nos comentários da pergunta.

Então, copiarei partes relevantes dessas 2 referências aqui. Dessa forma, podemos ter:

A melhor coisa a fazer para clonar objetos em C sharp!

Em primeiro lugar, essas são todas as nossas opções:

O artigo Fast Deep Copy by Expression Trees também tem comparação de desempenho de clonagem por serialização, reflexão e expressão.

Por que eu escolho o ICloneable (ou seja, manualmente)

Venkat Subramaniam (link redundante aqui) explica em detalhes o motivo .

Todo o artigo dele circula em torno de um exemplo que tenta ser aplicável na maioria dos casos, usando três objetos: Pessoa , Cérebro e Cidade . Queremos clonar uma pessoa, que terá seu próprio cérebro, mas a mesma cidade. Você pode imaginar todos os problemas que qualquer um dos outros métodos acima pode trazer ou ler o artigo.

Esta é a minha versão ligeiramente modificada de sua conclusão:

A cópia de um objeto especificando Newseguido pelo nome da classe geralmente leva ao código que não é extensível. O uso do clone, a aplicação do padrão de protótipo, é a melhor maneira de conseguir isso. No entanto, o uso do clone como é fornecido em C # (e Java) também pode ser bastante problemático. É melhor fornecer um construtor de cópias protegido (não público) e invocá-lo a partir do método clone. Isso nos permite delegar a tarefa de criar um objeto para uma instância da própria classe, fornecendo extensibilidade e, também, criando com segurança os objetos usando o construtor de cópia protegida.

Esperamos que esta implementação possa esclarecer as coisas:

public class Person : ICloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
    private int age;
    public Person(Brain aBrain, int theAge)
    {
        brain = aBrain; 
        age = theAge;
    }
    protected Person(Person another)
    {
        Brain refBrain = null;
        try
        {
            refBrain = (Brain) another.brain.clone();
            // You can set the brain in the constructor
        }
        catch(CloneNotSupportedException e) {}
        brain = refBrain;
        age = another.age;
    }
    public String toString()
    {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    public Object clone()
    {
        return new Person(this);
    }
    
}

Agora considere ter uma classe derivada de Person.

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

Você pode tentar executar o seguinte código:

public class User
{
    public static void play(Person p)
    {
        Person another = (Person) p.clone();
        System.out.println(p);
        System.out.println(another);
    }
    public static void main(String[] args)
    {
        Person sam = new Person(new Brain(), 1);
        play(sam);
        SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
        play(bob);
    }
}

A saída produzida será:

This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

Observe que, se mantivermos uma contagem do número de objetos, o clone, conforme implementado aqui, manterá uma contagem correta do número de objetos.

cregox
fonte
6
A Microsoft recomenda não usar ICloneablepara membros públicos. "Como os chamadores do Clone não podem depender do método que executa uma operação de clonagem previsível, recomendamos que o ICloneable não seja implementado em APIs públicas". msdn.microsoft.com/en-us/library/… No entanto, com base na explicação dada por Venkat Subramaniam em seu artigo vinculado, acho que faz sentido usar nessa situação , desde que os criadores dos objetos ICloneable tenham uma profunda compreensão de quais propriedades devem ser profundas cópias vs. rasos (ou seja, copiar profunda do cérebro, cópia superficial Cidade)
BateTech
Primeiro, estou longe de ser um especialista neste tópico (APIs públicas). Eu acho que uma vez que a observação MS faz muito sentido. E não acho seguro supor que os usuários dessa API tenham um entendimento tão profundo. Portanto, faz sentido implementá-lo em uma API pública se realmente não importa para quem vai usá-lo. Eu acho que ter algum tipo de UML explicitamente fazendo a distinção em cada propriedade pode ajudar. Mas eu gostaria de ouvir de alguém com mais experiência. : P
cregox
Você pode usar o CGbR Clone Generator e obter um resultado semelhante sem escrever manualmente o código.
Toxantron
Implementação Intermediate Language é útil
Michael Freidgeim
Não há final em C #
Konrad
84

Eu prefiro um construtor de cópias a um clone. A intenção é mais clara.

usuario
fonte
5
.Net não possui construtores de cópia.
Pop Catalin
48
Claro que sim: new MyObject (objToCloneFrom) Apenas declare um ctor que leva o objeto a clonar como parâmetro.
Nick
30
Não é a mesma coisa. Você precisa adicioná-lo a todas as aulas manualmente e nem sabe se está garantindo uma cópia detalhada.
Dave Van den Eynde 4/06/2009
15
+1 para cópia. Você também deve escrever manualmente uma função clone () para cada tipo de objeto, e boa sorte quando a hierarquia de classes atingir alguns níveis.
Andrew Grant
3
Com os construtores de cópia, você perde a hierarquia. agiledeveloper.com/articles/cloning072002.htm
Será
42

Método de extensão simples para copiar todas as propriedades públicas. Funciona para qualquer objeto e não exige que a classe seja [Serializable]. Pode ser estendido para outro nível de acesso.

public static void CopyTo( this object S, object T )
{
    foreach( var pS in S.GetType().GetProperties() )
    {
        foreach( var pT in T.GetType().GetProperties() )
        {
            if( pT.Name != pS.Name ) continue;
            ( pT.GetSetMethod() ).Invoke( T, new object[] 
            { pS.GetGetMethod().Invoke( S, null ) } );
        }
    };
}
Konstantin Salavatov
fonte
15
Infelizmente, isso é falho. É equivalente a chamar objectOne.MyProperty = objectTwo.MyProperty (isto é, apenas copiará a referência). Não clonará os valores das propriedades.
Alex Norcliffe
1
para Alex Norcliffe: autor da pergunta sobre "copiar cada propriedade" em vez de clonar. na maioria dos casos, a duplicação exata de propriedades não é necessária.
Konstantin Salavatov 28/03/12
1
Eu penso em usar esse método, mas com recursão. portanto, se o valor de uma propriedade for uma referência, crie um novo objeto e chame CopyTo novamente. Acabei de ver um problema, que todas as classes usadas devem ter um construtor sem parâmetros. Alguém já tentou isso? Também me pergunto se isso realmente funcionará com propriedades contendo classes .net como DataRow e DataTable?
Koryu
33

Acabei de criar um projeto de CloneExtensionsbiblioteca . Ele executa um clone rápido e profundo usando operações de atribuição simples geradas pela compilação do código de tempo de execução da Expression Tree.

Como usá-lo?

Em vez de escrever seus próprios métodos Cloneou Copycom um tom de designação entre campos e propriedades, faça com que o programa faça você mesmo, usando a Expression Tree. GetClone<T>()O método marcado como método de extensão permite simplesmente chamá-lo em sua instância:

var newInstance = source.GetClone();

Você pode escolher o que deve ser copiado sourcepara newInstanceusar CloningFlagsenum:

var newInstance 
    = source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);

O que pode ser clonado?

  • Primitivo (int, uint, byte, double, char, etc.), tipos imutáveis ​​conhecidos (DateTime, TimeSpan, String) e delegados (incluindo Action, Func, etc)
  • Anulável
  • Matrizes T []
  • Classes e estruturas personalizadas, incluindo classes e estruturas genéricas.

Os seguintes membros de classe / estrutura são clonados internamente:

  • Valores de campos públicos, e não somente leitura
  • Valores de propriedades públicas com acessadores get e set
  • Itens de coleção para tipos que implementam ICollection

Quão rápido é?

A solução é mais rápida que a reflexão, porque as informações dos membros precisam ser coletadas apenas uma vez, antes de GetClone<T>serem usadas pela primeira vez em um determinado tipo T.

Também é mais rápido que a solução baseada em serialização quando você clona mais do que duas instâncias do mesmo tipo T.

e mais...

Leia mais sobre expressões geradas na documentação .

Listagem de depuração de expressão de amostra para List<int>:

.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
    System.Collections.Generic.List`1[System.Int32] $source,
    CloneExtensions.CloningFlags $flags,
    System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
    .Block(System.Collections.Generic.List`1[System.Int32] $target) {
        .If ($source == null) {
            .Return #Label1 { null }
        } .Else {
            .Default(System.Void)
        };
        .If (
            .Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
        ) {
            $target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
            ).Invoke((System.Object)$source)
        } .Else {
            $target = .New System.Collections.Generic.List`1[System.Int32]()
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
        ) {
            .Default(System.Void)
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
        ) {
            .Block() {
                $target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
                    $source.Capacity,
                    $flags,
                    $initializers)
            }
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
        ) {
            .Block(
                System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
                System.Collections.Generic.ICollection`1[System.Int32] $var2) {
                $var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
                $var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
                .Loop  {
                    .If (.Call $var1.MoveNext() != False) {
                        .Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
                                $var1.Current,
                                $flags,


                         $initializers))
                } .Else {
                    .Break #Label2 { }
                }
            }
            .LabelTarget #Label2:
        }
    } .Else {
        .Default(System.Void)
    };
    .Label
        $target
    .LabelTarget #Label1:
}

}

o que tem o mesmo significado, como segue o código c #:

(source, flags, initializers) =>
{
    if(source == null)
        return null;

    if(initializers.ContainsKey(typeof(List<int>))
        target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
    else
        target = new List<int>();

    if((flags & CloningFlags.Properties) == CloningFlags.Properties)
    {
        target.Capacity = target.Capacity.GetClone(flags, initializers);
    }

    if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
    {
        var targetCollection = (ICollection<int>)target;
        foreach(var item in (ICollection<int>)source)
        {
            targetCollection.Add(item.Clone(flags, initializers));
        }
    }

    return target;
}

Não é exatamente como você escreveria seu próprio Clonemétodo List<int>?

MarcinJuraszek
fonte
2
Quais são as chances disso chegar ao NuGet? Parece a melhor solução. Como ele se compara ao NClone ?
esmagar
Eu acho que essa resposta deve ser votada mais vezes. A implementação manual do ICloneable é tediosa e propensa a erros, o uso de reflexão ou serialização é lento se o desempenho for importante e você precisar copiar milhares de objetos durante um curto período de tempo.
Nightcoder
De maneira alguma, você está errado sobre a reflexão, você deve simplesmente colocar isso em cache corretamente. Confira abaixo a minha resposta stackoverflow.com/a/34368738/4711853
Roma Borodov
31

Bem, eu estava tendo problemas ao usar o ICloneable no Silverlight, mas gostei da idéia de seralização, posso seralizar XML, então fiz o seguinte:

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //[email protected]
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}
Michael White
fonte
31

Se você já estiver usando um aplicativo de terceiros como o ValueInjecter ou o Automapper , poderá fazer algo assim:

MyObject oldObj; // The existing object to clone

MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax

Usando esse método, você não precisa implementar ISerializableou ICloneableem seus objetos. Isso é comum com o padrão MVC / MVVM, portanto, ferramentas simples como essa foram criadas.

veja o exemplo de clonagem profunda ValueInjecter no GitHub .

Michael Cox
fonte
26

O melhor é implementar um método de extensão como

public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }

e use-o em qualquer lugar da solução,

var copy = anyObject.DeepClone();

Podemos ter as três implementações a seguir:

  1. Por serialização (o código mais curto)
  2. Por Reflexão - 5x mais rápido
  3. Por árvores de expressão - 20x mais rápido

Todos os métodos vinculados estão funcionando bem e foram profundamente testados.

frakon
fonte
código de clonagem usando árvores de expressão que você publicou codeproject.com/Articles/1111658/… , está falhando com as versões mais recentes do .Net framework com uma exceção de segurança. A operação pode desestabilizar o tempo de execução , é basicamente uma exceção devido à árvore de expressão malformada, que é usado para gerar o Func em tempo de execução, verifique se você tem alguma solução. Na verdade, eu vi problemas apenas com objetos complexos com hierarquia profunda, um simples é facilmente copiado #
Mrinal Kamboj
1
A implementação do ExpressionTree parece muito boa. Até funciona com referências circulares e membros privados. Nenhum atributo é necessário. Melhor resposta que encontrei.
N73k 01/07/19
A melhor resposta, funcionou muito bem, você salvou meu dia
Adel Mourad
23

A resposta curta é que você herda da interface ICloneable e implementa a função .clone. O clone deve fazer uma cópia de membro e executar uma cópia profunda em qualquer membro que a exija e, em seguida, retornar o objeto resultante. Esta é uma operação recursiva (requer que todos os membros da classe que você deseja clonar sejam tipos de valor ou implementem o ICloneable e que seus membros sejam tipos de valor ou implementem o ICloneable e assim por diante).

Para uma explicação mais detalhada sobre a clonagem usando o ICloneable, consulte este artigo .

A resposta longa é "depende". Conforme mencionado por outros, o ICloneable não é suportado por genéricos, requer considerações especiais para referências de classe circular e é realmente visto por alguns como um "erro" no .NET Framework. O método de serialização depende de seus objetos serem serializáveis, o que pode não ser e você pode não ter controle. Ainda há muito debate na comunidade sobre qual é a "melhor prática". Na realidade, nenhuma das soluções é a melhor prática única para todas as situações em que o ICloneable foi originalmente interpretado.

Consulte o artigo deste canto do desenvolvedor para mais algumas opções (crédito a Ian).

Zach Burlingame
fonte
1
O ICloneable não possui uma interface genérica, portanto, não é recomendável usar essa interface.
Karg
Sua solução funciona até precisar lidar com referências circulares; depois, as coisas começam a complicar; é melhor tentar implementar a clonagem profunda usando serialização profunda.
Pop Catalin
Infelizmente, nem todos os objetos também podem ser serializados; portanto, nem sempre é possível usar esse método. O link de Ian é a resposta mais abrangente até agora.
Zach Burlingame
19
  1. Basicamente, você precisa implementar a interface ICloneable e realizar a cópia da estrutura do objeto.
  2. Se for uma cópia profunda de todos os membros, você precisará garantir (sem se relacionar com a solução que escolher) que todas as crianças também são clonáveis.
  3. Às vezes, você precisa estar ciente de alguma restrição durante esse processo, por exemplo, se você copiar os objetos ORM, a maioria das estruturas permite apenas um objeto anexado à sessão e NÃO DEVE criar clones desse objeto ou, se possível, precisa se preocupar. sobre a anexação de sessão desses objetos.

Felicidades.

dimarzionista
fonte
4
O ICloneable não possui uma interface genérica, portanto, não é recomendável usar essa interface.
Karg
Respostas simples e concisas são as melhores.
precisa saber é o seguinte
17

EDIT: projeto é descontinuado

Se você deseja clonagem verdadeira para tipos desconhecidos, consulte o fastclone .

É uma clonagem baseada em expressão que funciona cerca de 10 vezes mais rápido que a serialização binária e mantém a integridade completa do gráfico de objetos.

Isso significa: se você se referir várias vezes ao mesmo objeto em sua hierarquia, o clone também terá uma única instância sendo referenciada.

Não há necessidade de interfaces, atributos ou qualquer outra modificação nos objetos que estão sendo clonados.

Michael Sander
fonte
Este parece ser bastante útil
LuckyLikey
É mais fácil começar a trabalhar a partir de um instantâneo de código do que no sistema geral, especialmente um fechado. É bastante compreensível que nenhuma biblioteca possa resolver todos os problemas de uma só vez. Alguns relaxamentos devem ser feitos.
TarmoPikaro
1
Eu tentei sua solução e parece funcionar bem, obrigado! Eu acho que essa resposta deve ser votada mais vezes. A implementação manual do ICloneable é tediosa e propensa a erros, o uso de reflexão ou serialização é lento se o desempenho for importante e você precisar copiar milhares de objetos durante um curto período de tempo.
Nightcoder
Eu tentei e não funcionou para mim. Lança uma exceção MemberAccess.
Michael Brown
Ele não funciona com as versões mais recentes do .NET e é descontinuado #
Michael Sander
14

Mantenha as coisas simples e use o AutoMapper, como já mencionado, é uma pequena biblioteca simples para mapear um objeto para outro ... Para copiar um objeto para outro com o mesmo tipo, tudo o que você precisa é de três linhas de código:

MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);

O objeto de destino agora é uma cópia do objeto de origem. Não é simples o suficiente? Crie um método de extensão para usar em qualquer lugar da sua solução:

public static T Copy<T>(this T source)
{
    T copy = default(T);
    Mapper.CreateMap<T, T>();
    copy = Mapper.Map<T, T>(source);
    return copy;
}

O método de extensão pode ser usado da seguinte maneira:

MyType copy = source.Copy();
Empilhados
fonte
Tenha cuidado com este, ele executa muito mal. Acabei mudando para a resposta johnc, que é tão curta quanto esta e tem um desempenho muito melhor.
Agorilla
1
Isso faz apenas uma cópia superficial.
N73k 01/07/19
11

Eu vim com isso para superar uma falha do .NET que precisa copiar manualmente a Lista <T>.

Eu uso isso:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

E em outro lugar:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

Tentei criar o oneliner que faz isso, mas não é possível, devido ao rendimento não funcionar dentro de blocos de métodos anônimos.

Melhor ainda, use o clonador genérico List <T>:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}
Daniel Mošmondor
fonte
10

Q. Por que eu escolheria esta resposta?

  • Escolha esta resposta se desejar a velocidade mais rápida com a qual o .NET é capaz.
  • Ignore esta resposta se desejar um método muito, muito fácil de clonar.

Em outras palavras, escolha outra resposta, a menos que você tenha um gargalo de desempenho que precise ser corrigido e possa provar isso com um criador de perfil .

10x mais rápido que outros métodos

O método a seguir para executar um clone profundo é:

  • 10x mais rápido do que qualquer coisa que envolva serialização / desserialização;
  • Muito perto da velocidade máxima teórica em que o .NET é capaz.

E o método ...

Para velocidade máxima, você pode usar Nested MemberwiseClone para fazer uma cópia detalhada . É quase a mesma velocidade que copiar uma estrutura de valor e é muito mais rápido que (a) reflexão ou (b) serialização (conforme descrito em outras respostas nesta página).

Observe que se você usar o Nested MemberwiseClone para obter uma cópia detalhada , precisará implementar manualmente um ShallowCopy para cada nível aninhado na classe e um DeepCopy que chame todos os métodos ShallowCopy para criar um clone completo. Isso é simples: apenas algumas linhas no total, veja o código de demonstração abaixo.

Aqui está a saída do código que mostra a diferença de desempenho relativa para 100.000 clones:

  • 1,08 segundos para Nested MemberwiseClone em estruturas aninhadas
  • 4,77 segundos para Nested MemberwiseClone em classes aninhadas
  • 39,93 segundos para serialização / desserialização

Usar o Nested MemberwiseClone em uma classe quase tão rápido quanto copiar uma estrutura, e copiar uma estrutura é muito parecido com a velocidade máxima teórica em que o .NET é capaz.

Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
  Create Bob
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Clone Bob >> BobsSon
  Adjust BobsSon details
    BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Elapsed time: 00:00:04.7795670,30000000

Demo 2 of shallow and deep copy, using structs and value copying:
  Create Bob
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Clone Bob >> BobsSon
  Adjust BobsSon details:
    BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Elapsed time: 00:00:01.0875454,30000000

Demo 3 of deep copy, using class and serialize/deserialize:
  Elapsed time: 00:00:39.9339425,30000000

Para entender como fazer uma cópia profunda usando MemberwiseCopy, aqui está o projeto de demonstração que foi usado para gerar os tempos acima:

// Nested MemberwiseClone example. 
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
    public Person(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    [Serializable] // Not required if using MemberwiseClone
    public class PurchaseType
    {
        public string Description;
        public PurchaseType ShallowCopy()
        {
            return (PurchaseType)this.MemberwiseClone();
        }
    }
    public PurchaseType Purchase = new PurchaseType();
    public int Age;
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person ShallowCopy()
    {
        return (Person)this.MemberwiseClone();
    }
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person DeepCopy()
    {
            // Clone the root ...
        Person other = (Person) this.MemberwiseClone();
            // ... then clone the nested class.
        other.Purchase = this.Purchase.ShallowCopy();
        return other;
    }
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
    public PersonStruct(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    public struct PurchaseType
    {
        public string Description;
    }
    public PurchaseType Purchase;
    public int Age;
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct ShallowCopy()
    {
        return (PersonStruct)this;
    }
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct DeepCopy()
    {
        return (PersonStruct)this;
    }
}
// Added only for a speed comparison.
public class MyDeepCopy
{
    public static T DeepCopy<T>(T obj)
    {
        object result = null;
        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, obj);
            ms.Position = 0;
            result = (T)formatter.Deserialize(ms);
            ms.Close();
        }
        return (T)result;
    }
}

Em seguida, chame a demonstração do main:

void MyMain(string[] args)
{
    {
        Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
        var Bob = new Person(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {               
        Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
        var Bob = new PersonStruct(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details:\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);                
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {
        Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
        int total = 0;
        var sw = new Stopwatch();
        sw.Start();
        var Bob = new Person(30, "Lamborghini");
        for (int i = 0; i < 100000; i++)
        {
            var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
            total += BobsSon.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
    }
    Console.ReadKey();
}

Novamente, observe que, se você usar Nested MemberwiseClone para uma cópia profunda , precisará implementar manualmente um ShallowCopy para cada nível aninhado na classe e um DeepCopy que chame todos os métodos ShallowCopy para criar um clone completo. Isso é simples: apenas algumas linhas no total, veja o código de demonstração acima.

Tipos de valor vs. tipos de referências

Observe que quando se trata de clonar um objeto, há uma grande diferença entre uma " estrutura " e uma " classe ":

  • Se você tem um " struct ", é um tipo de valor, então você pode copiá-lo e o conteúdo será clonado (mas ele fará apenas um clone superficial, a menos que você use as técnicas deste post).
  • Se você tem uma " classe ", é um tipo de referência ; portanto, se você a copiar, tudo o que você está fazendo é copiar o ponteiro para ela. Para criar um clone verdadeiro, você precisa ser mais criativo e usar diferenças entre tipos de valor e tipos de referência, o que cria outra cópia do objeto original na memória.

Veja as diferenças entre tipos de valor e tipos de referência .

Soma de verificação para ajudar na depuração

  • A clonagem incorreta de objetos pode levar a erros muito difíceis de identificar. No código de produção, eu tendem a implementar uma soma de verificação para verificar se o objeto foi clonado corretamente e não foi corrompido por outra referência a ele. Essa soma de verificação pode ser desativada no modo Release.
  • Acho esse método bastante útil: geralmente, você só quer clonar partes do objeto, não a coisa toda.

Realmente útil para dissociar muitos threads de muitos outros threads

Um excelente caso de uso para esse código é alimentar clones de uma classe ou estrutura aninhada em uma fila, para implementar o padrão produtor / consumidor.

  • Podemos ter um (ou mais) threads modificando uma classe que eles possuem e, em seguida, enviar uma cópia completa dessa classe para um ConcurrentQueue .
  • Em seguida, temos um (ou mais) tópicos puxando cópias dessas classes e lidando com elas.

Isso funciona muito bem na prática e permite dissociar muitos threads (os produtores) de um ou mais threads (os consumidores).

E esse método também é incrivelmente rápido: se usarmos estruturas aninhadas, será 35x mais rápido que serializar / desserializar classes aninhadas e nos permitirá tirar proveito de todos os threads disponíveis na máquina.

Atualizar

Aparentemente, o ExpressMapper é tão rápido, se não mais rápido, quanto a codificação manual, como acima. Talvez eu precise ver como eles se comparam com um criador de perfil.

Contango
fonte
Se você copiar uma estrutura e obter uma cópia superficial, poderá ainda precisar de uma implementação específica para uma cópia profunda.
Lasse V. Karlsen
@Lasse V. Karlsen. Sim, você está absolutamente correto, atualizei a resposta para deixar isso mais claro. Este método pode ser usado para fazer cópias profundas de estruturas e classes. Você pode executar o exemplo de código de demonstração incluído para mostrar como é feito, ele tem um exemplo de clonagem profunda de uma estrutura aninhada e outro exemplo de clonagem profunda de uma classe aninhada.
Contango 04/07
9

Em geral, você implementa a interface ICloneable e o Clone. Os objetos C # têm um método MemberwiseClone interno que executa uma cópia superficial que pode ajudá-lo em todas as primitivas.

Para uma cópia profunda, não há como saber como fazê-lo automaticamente.

HappyDude
fonte
O ICloneable não possui uma interface genérica, portanto, não é recomendável usar essa interface.
Karg
8

Eu já o vi implementado através da reflexão. Basicamente, havia um método que iria percorrer os membros de um objeto e copiá-los adequadamente para o novo objeto. Quando alcançou tipos ou coleções de referência, acho que fez uma chamada recursiva em si mesma. A reflexão é cara, mas funcionou muito bem.

xr280xr
fonte
8

Aqui está uma implementação de cópia profunda:

public static object CloneObject(object opSource)
{
    //grab the type and create a new instance of that type
    Type opSourceType = opSource.GetType();
    object opTarget = CreateInstanceOfType(opSourceType);

    //grab the properties
    PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

    //iterate over the properties and if it has a 'set' method assign it from the source TO the target
    foreach (PropertyInfo item in opPropertyInfo)
    {
        if (item.CanWrite)
        {
            //value types can simply be 'set'
            if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
            {
                item.SetValue(opTarget, item.GetValue(opSource, null), null);
            }
            //object/complex types need to recursively call this method until the end of the tree is reached
            else
            {
                object opPropertyValue = item.GetValue(opSource, null);
                if (opPropertyValue == null)
                {
                    item.SetValue(opTarget, null, null);
                }
                else
                {
                    item.SetValue(opTarget, CloneObject(opPropertyValue), null);
                }
            }
        }
    }
    //return the new item
    return opTarget;
}
dougajmcdonald
fonte
2
Isto parece clone memberwise porque não cientes de propriedades tipo de referência
SLL
1
Se você deseja um desempenho incrivelmente rápido, não use esta implementação: ela usa reflexão, para que não seja tão rápida. Por outro lado, "a otimização prematura é a de todo mal", então ignore o lado do desempenho até depois de executar um criador de perfil.
Contango
1
CreateInstanceOfType não está definido?
MonsterMMORPG
Ele falha no interger: "O método não estático requer um destino".
Mr.B
8

Como não consegui encontrar um clonador que atenda a todos os meus requisitos em diferentes projetos, criei um clonador profundo que pode ser configurado e adaptado a diferentes estruturas de código, em vez de adaptá-lo para atender aos requisitos dos clonadores. Isso é conseguido adicionando anotações ao código que deve ser clonado ou você apenas deixa o código como deve ter o comportamento padrão. Ele usa reflexão, digite caches e baseia-se no efeito mais rápido . O processo de clonagem é muito rápido para uma enorme quantidade de dados e uma alta hierarquia de objetos (em comparação com outros algoritmos baseados em reflexão / serialização).

https://github.com/kalisohn/CloneBehave

Também disponível como pacote nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0

Por exemplo: O código a seguir fará o deepClone Address, mas apenas executará uma cópia superficial do campo _currentJob.

public class Person 
{
  [DeepClone(DeepCloneBehavior.Shallow)]
  private Job _currentJob;      

  public string Name { get; set; }

  public Job CurrentJob 
  { 
    get{ return _currentJob; }
    set{ _currentJob = value; }
  }

  public Person Manager { get; set; }
}

public class Address 
{      
  public Person PersonLivingHere { get; set; }
}

Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");

Address adrClone = adr.Clone();

//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true
Kalisohn
fonte
7

Gerador de código

Vimos muitas idéias desde a serialização, passando pela implementação manual até a reflexão, e quero propor uma abordagem totalmente diferente usando o CGbR Code Generator . O método generate clone é eficiente em memória e CPU e, portanto, 300x mais rápido que o DataContractSerializer padrão.

Tudo o que você precisa é de uma definição de classe parcial ICloneablee o gerador faz o resto:

public partial class Root : ICloneable
{
    public Root(int number)
    {
        _number = number;
    }
    private int _number;

    public Partial[] Partials { get; set; }

    public IList<ulong> Numbers { get; set; }

    public object Clone()
    {
        return Clone(true);
    }

    private Root()
    {
    }
} 

public partial class Root
{
    public Root Clone(bool deep)
    {
        var copy = new Root();
        // All value types can be simply copied
        copy._number = _number; 
        if (deep)
        {
            // In a deep clone the references are cloned 
            var tempPartials = new Partial[Partials.Length];
            for (var i = 0; i < Partials.Length; i++)
            {
                var value = Partials[i];
                value = value.Clone(true);
                tempPartials[i] = value;
            }
            copy.Partials = tempPartials;
            var tempNumbers = new List<ulong>(Numbers.Count);
            for (var i = 0; i < Numbers.Count; i++)
            {
                var value = Numbers[i];
                tempNumbers.Add(value);
            }
            copy.Numbers = tempNumbers;
        }
        else
        {
            // In a shallow clone only references are copied
            copy.Partials = Partials; 
            copy.Numbers = Numbers; 
        }
        return copy;
    }
}

Nota: A versão mais recente possui verificações mais nulas, mas as deixei de fora para melhor entendimento.

Toxantrona
fonte
6

Eu gosto de Copyconstructors assim:

    public AnyObject(AnyObject anyObject)
    {
        foreach (var property in typeof(AnyObject).GetProperties())
        {
            property.SetValue(this, property.GetValue(anyObject));
        }
        foreach (var field in typeof(AnyObject).GetFields())
        {
            field.SetValue(this, field.GetValue(anyObject));
        }
    }

Se você tiver mais coisas para copiar, adicione-as

LuckyLikey
fonte
6

Este método resolveu o problema para mim:

private static MyObj DeepCopy(MyObj source)
        {

            var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

            return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);

        }

Use-o assim: MyObj a = DeepCopy(b);

JerryGoyal
fonte
6

Aqui uma solução rápida e fácil que funcionou para mim sem retransmitir a serialização / desserialização.

public class MyClass
{
    public virtual MyClass DeepClone()
    {
        var returnObj = (MyClass)MemberwiseClone();
        var type = returnObj.GetType();
        var fieldInfoArray = type.GetRuntimeFields().ToArray();

        foreach (var fieldInfo in fieldInfoArray)
        {
            object sourceFieldValue = fieldInfo.GetValue(this);
            if (!(sourceFieldValue is MyClass))
            {
                continue;
            }

            var sourceObj = (MyClass)sourceFieldValue;
            var clonedObj = sourceObj.DeepClone();
            fieldInfo.SetValue(returnObj, clonedObj);
        }
        return returnObj;
    }
}

EDIT : requer

    using System.Linq;
    using System.Reflection;

Foi assim que eu o usei

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}
Daniele D.
fonte
5

Siga esses passos:

  • Defina an ISelf<T>com uma Selfpropriedade somente leitura que retorne Te ICloneable<out T>, que deriva de ISelf<T>e inclui um método T Clone().
  • Em seguida, defina um CloneBasetipo que implementa uma protected virtual generic VirtualCloneconversão MemberwiseClonepara o tipo passado.
  • Cada tipo derivado deve ser implementado VirtualClonechamando o método de clone base e, em seguida, fazendo o que for necessário para clonar adequadamente os aspectos do tipo derivado que o método VirtualClone pai ainda não manipulou.

Para máxima versatilidade de herança, as classes que expõem a funcionalidade de clonagem pública devem ser sealed, mas derivam de uma classe base que, de outra forma, é idêntica, exceto pela falta de clonagem. Ao invés de passar variáveis do tipo clonable explícita, ter um parâmetro do tipo ICloneable<theNonCloneableType>. Isso permitirá que uma rotina que espera que um derivado clonável Foofuncione com um derivado clonável de DerivedFoo, mas também permita a criação de derivativos não clonáveis ​​de Foo.

supercat
fonte
5

Eu acho que você pode tentar isso.

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
Sudhanva Kotabagi
fonte
4

Eu criei uma versão da resposta aceita que funciona com '[Serializable]' e '[DataContract]'. Já faz um tempo desde que o escrevi, mas se bem me lembro, o [DataContract] precisava de um serializador diferente.

Requer System, System.IO, System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary, System.Xml ;

public static class ObjectCopier
{

    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (typeof(T).IsSerializable == true)
        {
            return CloneUsingSerializable<T>(source);
        }

        if (IsDataContract(typeof(T)) == true)
        {
            return CloneUsingDataContracts<T>(source);
        }

        throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]'
    /// </summary>
    /// <remarks>
    /// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
    /// Uses code found on CodeProject, which allows free use in third party apps
    /// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
    /// </remarks>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingSerializable<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingDataContracts<T>(T source)
    {
        if (IsDataContract(typeof(T)) == false)
        {
            throw new ArgumentException("The type must be a data contract.", "source");
        }

        // ** Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        DataContractSerializer dcs = new DataContractSerializer(typeof(T));
        using(Stream stream = new MemoryStream())
        {
            using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
            {
                dcs.WriteObject(writer, source);
                writer.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
                {
                    return (T)dcs.ReadObject(reader);
                }
            }
        }
    }


    /// <summary>
    /// Helper function to check if a class is a [DataContract]
    /// </summary>
    /// <param name="type">The type of the object to check.</param>
    /// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
    public static bool IsDataContract(Type type)
    {
        object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
        return attributes.Length == 1;
    }

} 
Jeroen Ritmeijer
fonte
4

Ok, há algum exemplo óbvio com reflexão neste post, mas a reflexão geralmente é lenta, até que você comece a armazená-la corretamente.

se você o armazenar em cache corretamente, clonará profundamente o objeto 1000000 em 4,6s (medido pelo Watcher).

static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

do que você pega propriedades em cache ou adiciona novas ao dicionário e as usa simplesmente

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}

código completo cheque no meu post em outra resposta

https://stackoverflow.com/a/34365709/4711853

Roma Borodov
fonte
2
A chamada prop.GetValue(...)ainda é reflexo e não pode ser armazenada em cache. Em uma árvore de expressão sua compilado embora, por isso mais rápido
Tseng
4

Como quase todas as respostas a essa pergunta foram insatisfatórias ou claramente não funcionam na minha situação, criei o AnyClone que é totalmente implementado com reflexão e resolvi todas as necessidades aqui. Não consegui fazer com que a serialização funcionasse em um cenário complicado com estrutura complexa e IClonableé menos do que o ideal - na verdade, nem deveria ser necessário.

Padrão ignorar atributos são suportados usando [IgnoreDataMember], [NonSerialized]. Suporta coleções complexas, propriedades sem setters, campos somente leitura etc.

Espero que ajude alguém por aí que tenha os mesmos problemas que eu.

Michael Brown
fonte
4

Disclaimer: Eu sou o autor do pacote mencionado.

Fiquei surpreso em saber como as principais respostas para essa pergunta em 2019 ainda usam serialização ou reflexão.

A serialização é limitadora (requer atributos, construtores específicos etc.) e é muito lenta

BinaryFormatterrequer o Serializableatributo, JsonConverterrequer um construtor sem parâmetros ou atributos, nem manipula campos ou interfaces somente de leitura muito bem e ambos são 10 a 30 vezes mais lentos que o necessário.

Árvores de expressão

Em vez disso, você pode usar Expression Trees ou Reflection.Emit para gerar código de clonagem apenas uma vez e, em seguida, usar esse código compilado em vez de reflexão lenta ou serialização.

Depois de me deparar com o problema e não encontrar uma solução satisfatória, decidi criar um pacote que faz exatamente isso e funciona com todos os tipos e é quase tão rápido quanto um código escrito personalizado .

Você pode encontrar o projeto no GitHub: https://github.com/marcelltoth/ObjectCloner

Uso

Você pode instalá-lo a partir do NuGet. Pegue o ObjectClonerpacote e use-o como:

var clone = ObjectCloner.DeepClone(original);

ou se você não se importa de poluir o seu tipo de objeto com extensões, obtenha ObjectCloner.Extensionstambém e escreva:

var clone = original.DeepClone();

atuação

Um benchmark simples de clonagem de uma hierarquia de classes mostrou desempenho ~ 3x mais rápido que o Reflection, ~ 12x mais rápido que a serialização Newtonsoft.Json e ~ 36x mais rápido do que o sugerido BinaryFormatter.

Marcell Toth
fonte