it-swarm-pt.tech

Objetos de clonagem profunda

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.

Geralmente não preciso dessa funcionalidade, então, quando necessário, recorri à criação de um novo objeto e à cópia de cada propriedade individualmente, mas sempre me dá a sensação de que existe uma maneira melhor ou mais elegante de lidar com isso. a situação.

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

2042
NakedBrunch

Embora a prática padrão seja implementar a interface ICloneable (descrita aqui , então eu não regurgitarei), aqui está uma copiadora de objeto clone profunda que eu encontrei em The Code Project um tempo atrás e incorporou 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.", "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 ideia é que ele serialize seu objeto e, em seguida, desserialize-o em 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 o new extension methods 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 (Newtonsoft) Json para fazer isso, deve ser mais claro e evita a sobrecarga de tags [Serializable]. (NB @atconway apontou 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);
}
1600
johnc

Eu queria um clonador para objetos muito simples, principalmente primitivos e listas. Se o seu objeto estiver fora da caixa JSON serializável, então este método fará o truque. Isso não requer nenhuma 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);
    }
}
244
craastad

A razão para não usar ICloneable é não porque não tem uma interface genérica. A razão para não usá-lo é porque é vago . Não deixa claro se você está obtendo uma cópia superficial ou profunda; Isso depende do implementador.

Sim, MemberwiseClone faz uma cópia superficial, mas o oposto de MemberwiseClone não é Clone; seria, talvez, DeepClone, que não existe. Quando você usa um objeto por meio de sua interface ICloneable, não é possível saber qual tipo de clonagem o objeto subjacente executa. (E os comentários XML não deixarão isso claro, porque você obterá os comentários da interface em vez daqueles no método Clone do objeto.)

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

163
Ryan Lundy

Depois de ler muito sobre muitas das opções ligadas aqui, e possíveis soluções para este problema, eu acredito todas as opções estão bem resumidas no link Ian P (todas as outras opções são variações delas) e a melhor solução é fornecida por o link de Pedro77 nos comentários da questão.

Então, vou apenas copiar partes relevantes dessas duas 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 por Árvores de Expressão também tem comparação de desempenho de clonagem por Serialização, Reflexão e Árvores de Expressão.

Por que eu escolho ICloneable (por exemplo, manualmente)

Sr. Venkat Subramaniam (link redundante aqui) explica com muito detalhe porque .

Todo o seu artigo gira em torno de um exemplo que tenta ser aplicável para a maioria dos casos, usando 3 objetos: Person , Brain 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:

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

Espero 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 da Pessoa.

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 [email protected]
This is person with [email protected]
SkilledPerson: This is person with [email protected]
SkilledPerson: This is person with [email protected]

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

102
cregox

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

77
Nick

Método de extensão simples para copiar todas as propriedades públicas. Funciona para qualquer objeto e não requer 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 ) } );
        }
    };
}
38
Konstantin Salavatov

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

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);
    }
}
30
Michael White

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

Como usá-lo?

Em vez de escrever seus próprios métodos Clone ou Copy com um tom de atribuições entre campos e propriedades, faça o programa fazer isso sozinho, usando a Árvore de Expressões. GetClone<T>() método marcado como método de extensão permite que você simplesmente o chame em sua instância:

var newInstance = source.GetClone();

Você pode escolher o que deve ser copiado de source para newInstance usando CloningFlags enum:

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 customizadas, incluindo classes e estruturas genéricas.

Os membros da classe/struct a seguir são clonados internamente:

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

Quão rápido é?

A solução é mais rápida que a reflexão, porque as informações dos membros devem ser reunidas apenas uma vez, antes que GetClone<T> seja usado pela primeira vez para um determinado tipo T.

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

e mais ...

Leia mais sobre expressões geradas em documentation .

Exemplo de listagem de depuração de expressão 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 seguir 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 é bem como você escreveria seu próprio método Clone para List<int>?

27
MarcinJuraszek

Se você já estiver usando um aplicativo de terceiros como ValueInjecter ou Automapper , você pode 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 ISerializable ou ICloneable em seus objetos. Isso é comum com o padrão MVC/MVVM, portanto, ferramentas simples como essa foram criadas.

veja a solução de clonagem profunda valueinjecter no CodePlex .

26
Michael Cox

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

Para uma explicação mais detalhada sobre Clonagem usando ICloneable, confira este artigo .

A resposta longa é "depende". Como mencionado por outros, 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 eles podem 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 é do tamanho único para todas as melhores práticas para todas as situações, como ICloneable foi originalmente interpretado como sendo.

Veja o seguinte artigo do Developer's Corner para mais algumas opções (crédito para Ian).

20
Zach Burlingame

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

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

e depois usá-lo em qualquer lugar na solução por

var copy = anyObject.DeepClone();

Podemos ter as seguintes três implementações:

  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.

19
frakon
  1. Basicamente, você precisa implementar a interface ICloneable e depois realizar a cópia da estrutura do objeto.
  2. Se for uma cópia profunda de todos os membros, você precisará assegurar (não relacionado à solução que você escolher) que todas as crianças também podem ser clonadas.
  3. Às vezes você precisa estar ciente de alguma restrição durante este processo, por exemplo, se você copiar os objetos ORM, a maioria das estruturas permite apenas um objeto anexado à sessão e você NÃO DEVE fazer clones desse objeto, ou se for possível, você precisa se preocupar sobre a anexação de sessão desses objetos.

Felicidades.

16
dimarzionist

Se você quiser clonagem verdadeira para tipos desconhecidos, você pode dar uma olhada em fastclone .

Isso é clonagem baseada em expressão, trabalhando cerca de 10 vezes mais rápido que a serialização binária e mantendo a integridade completa do gráfico de objeto.

Isso significa que: 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.

15
Michael Sander

Mantenha as coisas simples e use AutoMapper como outros mencionaram, é uma pequena biblioteca simples para mapear um objeto para outro ... Para copiar um objeto para outro com o mesmo tipo, tudo 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 todos os lugares 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;
}

Usando o método de extensão, as três linhas se tornam uma linha:

MyType copy = source.Copy();
11
Stacked

Eu vim com isso para superar um .net falta ter que copiar manualmente em profundidade List <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;
}

Eu tentei criar um oneliner que faz isso, mas não é possível, porque não funciona dentro de blocos de métodos anônimos.

Melhor ainda, use o cloner 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();
        }
    }
}
10
Daniel Mošmondor

P. Por que eu escolheria essa resposta?

  • Escolha esta resposta se você quiser a velocidade mais rápida que o .NET é capaz.
  • Ignore esta resposta se você quiser um método realmente muito fácil de clonagem.

Em outras palavras, vá com outra resposta a menos que você tenha um afunilamento de desempenho que precise ser consertado e você 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 que qualquer coisa que envolva serialização/desserialização;
  • Muito perto da velocidade máxima teórica que o .NET é capaz de fazer.

E o método ...

Para maior velocidade, você pode usar o MemberwiseClone aninhado para fazer uma cópia profunda . É quase a mesma velocidade que copiar um struct de valor e é muito mais rápido do que (a) reflexão ou (b) serialização (como descrito em outras respostas nesta página).

Note que if você usa Nested MemberwiseClone para uma cópia profunda , você tem para implementar manualmente um ShallowCopy para cada nível aninhado na classe e um DeepCopy que chama 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 relativo para 100.000 clones:

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

Usando Nested MemberwiseClone em uma classe quase tão rápido quanto copiar uma estrutura, e copiar uma estrutura é muito próximo da velocidade máxima teórica que o .NET é capaz de fazer.

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 detalhada usando o MemberwiseCopy, aqui está o projeto de demonstração que foi usado para gerar os horários 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;
    }
}

Então, ligue para 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, note que if você usa MemberwiseClone aninhado para uma cópia profunda , você tem que implementar manualmente um ShallowCopy para cada nível aninhado na classe e um DeepCopy que chama 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 versus tipos de referências

Note que quando se trata de clonar um objeto, existe uma grande diferença entre um " struct " e um " classe ":

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

Veja diferenças entre tipos de valores e tipos de referências .

Checksums para ajudar na depuração

  • Clonar objetos incorretamente pode levar a erros muito difíceis de serem soltos. No código de produção, tenho a tendência de implementar uma soma de verificação para verificar novamente se o objeto foi clonado corretamente e não foi corrompido por outra referência a ele. Esta soma de verificação pode ser desativada no modo Release.
  • Eu acho este método bastante útil: muitas vezes, você só quer clonar partes do objeto, não a coisa toda.

Realmente útil para desvincular muitos threads de muitos outros threads

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

  • Podemos ter um (ou mais) tópicos modificando uma classe que eles possuam, então empurrando uma cópia completa desta classe para um ConcurrentQueue.
  • Temos então um (ou mais) tópicos puxando cópias dessas classes e lidando com elas.

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

E esse método é extremamente rápido também: se usarmos estruturas aninhadas, é 35x mais rápido que serializar/desserializar classes aninhadas e nos permite aproveitar todos os threads disponíveis na máquina.

Atualizar

Aparentemente, o ExpressMapper é tão rápido, se não mais rápido, que a codificação manual, como acima. Eu poderia ter que ver como eles se comparam com um profiler.

8
Contango

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

7
xr280xr

Como não consegui encontrar um clonador que atenda a todos os meus requisitos em projetos diferentes, criei um clonador profundo que pode ser configurado e adaptado a diferentes estruturas de código em vez de adaptar meu código para atender aos requisitos de clonagem. É conseguido adicionando anotações ao código que deve ser clonado ou você simplesmente deixa o código como está para ter o comportamento padrão. Ele usa reflexão, tipo caches e é baseado em efeito 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.

Por exemplo: O código a seguir irá usar DeepClone Address, mas somente realizará 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
7
kalisohn

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

Para uma cópia profunda, não há como saber como fazer isso automaticamente.

7
HappyDude

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

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 assim: MyObj a = DeepCopy(b);

6
JerryGoyal

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ê tem mais coisas para copiar, adicione-as

5
LuckyLikey

Gerador de código

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

Tudo que você precisa é uma definição parcial de classe com ICloneable e 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 tem mais verificações de nulos, mas deixei-os de fora para melhor compreensão.

5
Toxantron

Aqui uma solução rápida e fácil que funcionou para mim sem retransmitir em 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: requires

    using System.Linq;
    using System.Reflection;

É assim que eu usei

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

Eu acho que você pode tentar isso.

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

Siga esses passos:

  • Defina um ISelf<T> com uma propriedade Self somente leitura que retorne T e ICloneable<out T>, que deriva de ISelf<T> e inclua um método T Clone().
  • Em seguida, defina um tipo CloneBase que implemente protected virtual generic VirtualClone casting MemberwiseClone para o tipo passado.
  • Cada tipo derivado deve implementar VirtualClone chamando o método clone base e, em seguida, fazendo o que precisa ser feito 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, classes expondo 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. Em vez de passar variáveis ​​do tipo explícito clonável, use um parâmetro do tipo ICloneable<theNonCloneableType>. Isto permitirá uma rotina que espera que uma derivada clonável de Foo trabalhe com uma derivada clonável de DerivedFoo, mas também permita a criação de derivadas não clonáveis ​​de Foo.

4
supercat

Eu criei uma versão da resposta aceita que funciona com '[Serializable]' e '[DataContract]'. Já faz um tempo desde que eu escrevi, mas se bem me lembro [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;
    }

} 
3
Jeroen Ritmeijer

Se a sua árvore de objetos for serializável, você também pode usar algo assim

static public MyClass Clone(MyClass myClass)
{
    MyClass clone;
    XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
    using (var ms = new MemoryStream())
    {
        ser.Serialize(ms, myClass);
        ms.Position = 0;
        clone = (MyClass)ser.Deserialize(ms);
    }
    return clone;
}

ser informado de que esta Solução é bastante fácil, mas não é tão eficiente quanto outras soluções podem ser.

E certifique-se de que, se a classe crescer, ainda haverá apenas os campos clonados, que também serão serializados.

3
LuckyLikey

Para clonar seu objeto de classe, você pode usar o método Object.MemberwiseClone,

basta adicionar essa função à sua turma:

public class yourClass
{
    // ...
    // ...

    public yourClass DeepCopy()
    {
        yourClass othercopy = (yourClass)this.MemberwiseClone();
        return othercopy;
    }
}

então para executar uma cópia independente profunda, apenas chame o método DeepCopy:

yourClass newLine = oldLine.DeepCopy();

espero que isto ajude.

3
Chtiwi Malek

Ok, existem alguns exemplos óbvios com reflexos neste post, MAS a reflexão é geralmente lenta, até que você comece a armazená-la corretamente.

se você fizer cache corretamente, então clone profundamente o objeto 1000000 por 4,6s (medido pelo Watcher).

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

do que você pegar propriedades em cache ou adicionar novo ao dicionário e usá-los simplesmente

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

código completo verifique no meu post em outra resposta

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

3
Roma Borodov

Como quase todas as respostas a esta pergunta foram insatisfatórias ou simplesmente não funcionam na minha situação, eu escrevi AnyClone que é inteiramente implementado com reflexão e resolveu 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 que ideal - na verdade, nem deveria ser necessário.

Atributos ignorar padrão são suportados usando [IgnoreDataMember], [NonSerialized]. Suporta coleções complexas, propriedades sem setters, campos readonly etc.

Espero que ajude alguém lá fora que teve os mesmos problemas que eu.

2
Michael Brown

Ao usar Marc Gravells protobuf-net como seu serializador, a resposta aceita precisa de algumas pequenas modificações, já que o objeto a ser copiado não será atribuído a [Serializable] e, portanto, não é serializável e o método Clone lançará uma exceção.
Eu modifiquei para trabalhar com protobuf-net:

public static T Clone<T>(this T source)
{
    if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
           == null)
    {
        throw new ArgumentException("Type has no ProtoContract!", "source");
    }

    if(Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

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

Isso verifica a presença de um atributo [ProtoContract] e usa formatador próprio protobufs para serializar o objeto.

1
Basti M

Extensão C # que suportará os tipos "not ISerializable" também.

 public static class AppExtensions
 {                                                                      
       public static T DeepClone<T>(this T a)
       {
           using (var stream = new MemoryStream())
           {
               var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

               serializer.Serialize(stream, a);
               stream.Position = 0;
               return (T)serializer.Deserialize(stream);
           }
       }                                                                    
 }

Uso

       var obj2 = obj1.DeepClone()
1
Sameera R.

É inacreditável quanto esforço você pode gastar com a interface IClonable - especialmente se você tiver hierarquias de classes pesadas. Também o MemberwiseClone funciona de alguma maneira estranhamente - ele não clona exatamente o tipo de estrutura normal.

E, claro, o dilema mais interessante para a serialização é serializar as referências anteriores - por exemplo, hierarquias de classes nas quais você tem relacionamentos pai-filho. Eu duvido que o serializador binário seja capaz de ajudá-lo neste caso. (Isso terminará com loops recursivos + estouro de pilha).

De alguma forma eu gostei de solução proposta aqui: Como você faz uma cópia profunda de um objeto no .net (c # especificamente)?

no entanto - ele não suporta Listas, acrescentou que o apoio, também levou em conta re-parenting. Para parenting apenas regra que eu fiz esse campo ou propriedade deve ser nomeado "pai", então ele será ignorado pelo DeepClone. Você pode querer decidir suas próprias regras para referências anteriores - para hierarquias de árvores pode ser "esquerda/direita", etc ...

Aqui está o snippet de código inteiro, incluindo o código de teste:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestDeepClone
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.name = "main_A";
            a.b_list.Add(new B(a) { name = "b1" });
            a.b_list.Add(new B(a) { name = "b2" });

            A a2 = (A)a.DeepClone();
            a2.name = "second_A";

            // Perform re-parenting manually after deep copy.
            foreach( var b in a2.b_list )
                b.parent = a2;


            Debug.WriteLine("ok");

        }
    }

    public class A
    {
        public String name = "one";
        public List<String> list = new List<string>();
        public List<String> null_list;
        public List<B> b_list = new List<B>();
        private int private_pleaseCopyMeAsWell = 5;

        public override string ToString()
        {
            return "A(" + name + ")";
        }
    }

    public class B
    {
        public B() { }
        public B(A _parent) { parent = _parent; }
        public A parent;
        public String name = "two";
    }


    public static class ReflectionEx
    {
        public static Type GetUnderlyingType(this MemberInfo member)
        {
            Type type;
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    type = ((FieldInfo)member).FieldType;
                    break;
                case MemberTypes.Property:
                    type = ((PropertyInfo)member).PropertyType;
                    break;
                case MemberTypes.Event:
                    type = ((EventInfo)member).EventHandlerType;
                    break;
                default:
                    throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
            }
            return Nullable.GetUnderlyingType(type) ?? type;
        }

        /// <summary>
        /// Gets fields and properties into one array.
        /// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
        /// </summary>
        /// <param name="type">Type from which to get</param>
        /// <returns>array of fields and properties</returns>
        public static MemberInfo[] GetFieldsAndProperties(this Type type)
        {
            List<MemberInfo> fps = new List<MemberInfo>();
            fps.AddRange(type.GetFields());
            fps.AddRange(type.GetProperties());
            fps = fps.OrderBy(x => x.MetadataToken).ToList();
            return fps.ToArray();
        }

        public static object GetValue(this MemberInfo member, object target)
        {
            if (member is PropertyInfo)
            {
                return (member as PropertyInfo).GetValue(target, null);
            }
            else if (member is FieldInfo)
            {
                return (member as FieldInfo).GetValue(target);
            }
            else
            {
                throw new Exception("member must be either PropertyInfo or FieldInfo");
            }
        }

        public static void SetValue(this MemberInfo member, object target, object value)
        {
            if (member is PropertyInfo)
            {
                (member as PropertyInfo).SetValue(target, value, null);
            }
            else if (member is FieldInfo)
            {
                (member as FieldInfo).SetValue(target, value);
            }
            else
            {
                throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
            }
        }

        /// <summary>
        /// Deep clones specific object.
        /// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
        /// This is now improved version (list support added)
        /// </summary>
        /// <param name="obj">object to be cloned</param>
        /// <returns>full copy of object.</returns>
        public static object DeepClone(this object obj)
        {
            if (obj == null)
                return null;

            Type type = obj.GetType();

            if (obj is IList)
            {
                IList list = ((IList)obj);
                IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);

                foreach (object elem in list)
                    newlist.Add(DeepClone(elem));

                return newlist;
            } //if

            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }
            else if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);

                for (int i = 0; i < array.Length; i++)
                    copied.SetValue(DeepClone(array.GetValue(i)), i);

                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
                object toret = Activator.CreateInstance(obj.GetType());

                MemberInfo[] fields = type.GetFieldsAndProperties();
                foreach (MemberInfo field in fields)
                {
                    // Don't clone parent back-reference classes. (Using special kind of naming 'parent' 
                    // to indicate child's parent class.
                    if (field.Name == "parent")
                    {
                        continue;
                    }

                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;

                    field.SetValue(toret, DeepClone(fieldValue));
                }

                return toret;
            }
            else
            {
                // Don't know that type, don't know how to clone it.
                if (Debugger.IsAttached)
                    Debugger.Break();

                return null;
            }
        } //DeepClone
    }

}
1
TarmoPikaro

Ainda outra resposta JSON.NET. Esta versão funciona com classes que não implementam ISerializable.

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
    }

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}
1
Matthew Watson

Um mapeador executa uma cópia profunda. Foreach membro do seu objeto cria um novo objeto e atribui todos os seus valores. Ele funciona recursivamente em cada membro interno não-primitivo.

Eu sugiro que você seja um dos mais rápidos, atualmente desenvolvidos. Sugiro o UltraMapper https://github.com/maurosampietro/UltraMapper

Pacotes Nuget: https://www.nuget.org/packages/UltraMapper/

1
Mauro Sampietro

As abordagens genéricas são todas tecnicamente válidas, mas eu só queria adicionar uma nota de mim, pois raramente precisamos realmente de uma cópia profunda real, e eu me oporia fortemente a usar cópia profunda genérica em aplicativos de negócios reais, pois isso faz com que você tenha muitos lugares onde os objetos são copiados e depois modificados explicitamente, é fácil se perder.

Na maioria das situações da vida real, você também quer ter tanto controle granular quanto possível sobre o processo de cópia, pois você não está apenas acoplado à estrutura de acesso a dados, mas também na prática os objetos de negócios copiados raramente devem ser 100% iguais. Pense em um exemplo de referenceId usado pelo ORM para identificar referências de objeto, uma cópia completa completa também copiará esse id, portanto, enquanto na memória os objetos serão diferentes, assim que você os enviar para o armazenamento de dados, ele irá reclamar, assim você tem que modificar essas propriedades manualmente após a cópia de qualquer maneira e se o objeto muda você precisa ajustá-lo em todos os lugares que usam a cópia profunda genérica.

Expandindo a resposta @cregox com o ICloneable, o que na verdade é uma cópia profunda? É apenas um objeto recém-alocado no heap que é idêntico ao objeto original, mas ocupa um espaço de memória diferente, como tal, em vez de usar uma funcionalidade genérica de cloner, porque não apenas criar um novo objeto?

Eu pessoalmente uso a idéia de métodos estáticos de fábrica em meus objetos de domínio.

Exemplo:

    public class Client
    {
        public string Name { get; set; }

        protected Client()
        {
        }

        public static Client Clone(Client copiedClient)
        {
            return new Client
            {
                Name = copiedClient.Name
            };
        }
    }

    public class Shop
    {
        public string Name { get; set; }

        public string Address { get; set; }

        public ICollection<Client> Clients { get; set; }

        public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
        {
            var copiedClients = new List<Client>();
            foreach (var client in copiedShop.Clients)
            {
                copiedClients.Add(Client.Clone(client));
            }

            return new Shop
            {
                Name = copiedShop.Name,
                Address = newAddress,
                Clients = copiedClients
            };
        }
    }

Se alguém está olhando como ele pode estruturar a instanciação de objetos enquanto retém o controle total sobre o processo de cópia, essa é uma solução com a qual eu pessoalmente tenho tido muito sucesso. Os construtores protegidos também fazem isso, outros desenvolvedores são forçados a usar os métodos de fábrica que fornecem um ponto único de instanciação de objeto encapsulando a lógica de construção dentro do objeto. Você também pode sobrecarregar o método e ter várias lógicas de clone para diferentes locais, se necessário.

0
Piotr Jerzy Mamenas

Eu encontrei uma nova maneira de fazer isso que é Emit.

Podemos usar Emit para adicionar o IL ao aplicativo e executá-lo. Mas eu não acho que seja um bom caminho para eu aperfeiçoar isso e escrever minha resposta.

O Emit pode ver o documento oficial e Guia

Você deve aprender um pouco de IL para ler o código. Vou escrever o código que pode copiar a propriedade na classe.

public static class Clone
{        
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        //see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>) CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
        {
            //do not copy static that will except
            if (temp.GetAccessors(true)[0].IsStatic)
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }

    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}

O código pode ser cópia profunda, mas pode copiar a propriedade. Se você quiser fazer uma cópia profunda que você pode mudar para o IL é muito difícil que eu não posso fazê-lo.

0
lindexi

que tal apenas reformulando dentro de um método que deve invocar basicamente um construtor de cópia automática

T t = new T();
T t2 = (T)t;  //eh something like that

        List<myclass> cloneum;
        public void SomeFuncB(ref List<myclass> _mylist)
        {
            cloneum = new List<myclass>();
            cloneum = (List < myclass >) _mylist;
            cloneum.Add(new myclass(3));
            _mylist = new List<myclass>();
        }

parece funcionar para mim

0
will_m

Isto irá copiar todas as propriedades legíveis e graváveis ​​de um objeto para outro.

 public class PropertyCopy<TSource, TTarget> 
                        where TSource: class, new()
                        where TTarget: class, new()
        {
            public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
            {
                if (src==null) return trg;
                if (trg == null) trg = new TTarget();
                var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
                if (properties != null && properties.Count() > 0)
                    fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
                if (fulllist == null || fulllist.Count() == 0) return trg;

                fulllist.ForEach(c =>
                    {
                        c.SetValue(trg, c.GetValue(src));
                    });

                return trg;
            }
        }

e é assim que você usa:

 var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
                                                            "Creation",
                                                            "Description",
                                                            "IdTicketStatus",
                                                            "IdUserCreated",
                                                            "IdUserInCharge",
                                                            "IdUserRequested",
                                                            "IsUniqueTicketGenerated",
                                                            "LastEdit",
                                                            "Subject",
                                                            "UniqeTicketRequestId",
                                                            "Visibility");

ou copiar tudo:

var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);
0
Ylli Prifti