simple Como você faz uma cópia profunda de um objeto no.net(c#especificamente)?




c# shallow copy (9)

Esta questão já tem uma resposta aqui:

Eu quero uma cópia verdadeira e profunda. Em Java, isso foi fácil, mas como você faz isso em C #?


    public static object CopyObject(object input)
    {
        if (input != null)
        {
            object result = Activator.CreateInstance(input.GetType());
            foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList))
            {
                if (field.FieldType.GetInterface("IList", false) == null)
                {
                    field.SetValue(result, field.GetValue(input));
                }
                else
                {
                    IList listObject = (IList)field.GetValue(result);
                    if (listObject != null)
                    {
                        foreach (object item in ((IList)field.GetValue(input)))
                        {
                            listObject.Add(CopyObject(item));
                        }
                    }
                }
            }
            return result;
        }
        else
        {
            return null;
        }
    }

Dessa forma, algumas vezes mais rápido que BinarySerialization E isso não requer o atributo [Serializable] .


Eu vi algumas abordagens diferentes para isso, mas eu uso um método utilitário genérico como tal:

public static T DeepClone<T>(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

Notas:

  • Sua classe DEVE ser marcada como [Serializable] para que isso funcione.
  • Seu arquivo de origem deve incluir o seguinte código:

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

Eu acredito que a abordagem BinaryFormatter é relativamente lenta (o que foi uma surpresa para mim!). Você pode usar o ProtoBuf .NET para alguns objetos se eles atenderem aos requisitos do ProtoBuf. A partir da página ProtoBuf Getting Started ( http://code.google.com/p/protobuf-net/wiki/GettingStarted ):

Notas sobre os tipos suportados:

Classes personalizadas que:

  • São marcados como contrato de dados
  • Tem um construtor sem parâmetros
  • Para o Silverlight: são públicos
  • Muitos primitivos comuns, etc.
  • Matrizes de dimensão única : T []
  • Listar <T> / IList <T>
  • Dicionário <TKey, TValue> / IDictionary <TKey, TValue>
  • qualquer tipo que implemente IEnumerable <T> e tenha um método Add (T)

O código pressupõe que os tipos serão mutáveis ​​em torno dos membros eleitos. Consequentemente, as estruturas personalizadas não são suportadas, pois devem ser imutáveis.

Se sua turma atender a esses requisitos, você pode tentar:

public static void deepCopy<T>(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        Serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = Serializer.Deserialize<T>(stream);
    }
}

Que é muito rápido mesmo ...

Editar:

Aqui está o código de trabalho para uma modificação disso (testado no .NET 4.6). Ele usa System.Xml.Serialization e System.IO. Não há necessidade de marcar as classes como serializáveis.

public void DeepCopy<T>(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        var serializer = new XS.XmlSerializer(typeof(T));

        serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = (T)serializer.Deserialize(stream);
    }
}

Você pode usar o MemberwiseClone aninhado para fazer uma cópia detalhada . É quase a mesma velocidade que copiar um valor struct e é uma ordem de magnitude mais rápida do que (a) reflexão ou (b) serialização (como descrito em outras respostas nesta página).

Observe que, se você usar o Nested MemberwiseClone para uma cópia profunda , terá 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 abaixo.

Aqui está a saída do código mostrando a diferença de desempenho relativo (4,77 segundos para MemberwiseCopy aninhado em profundidade vs. 39,93 segundos para serialização). Usar o MemberwiseCopy aninhado é quase tão rápido quanto copiar uma estrutura, e copiar uma estrutura é muito próximo da velocidade máxima teórica com a qual o .NET é capaz.

    Demo 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 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 of deep copy, using class and serialize/deserialize:
      Elapsed time: 00:00:39.9339425,30000000

Para entender como fazer uma cópia profunda usando o MemberwiseCopy, aqui está o projeto de demonstração:

// 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 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", sw.Elapsed, total);
        }
        {               
            Console.Write("Demo 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", sw.Elapsed, total);
        }
        {
            Console.Write("Demo 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 o Nested MemberwiseClone para uma cópia profunda , precisará 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.

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

  • Se você tem uma "struct", é um tipo de valor para que você possa copiá-lo, e o conteúdo será clonado.
  • 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 um método que crie outra cópia do objeto original na memória.
  • 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. Também é essencial para qualquer caso de uso em que você esteja modificando objetos e, em seguida, alimentando as cópias modificadas em uma fila.

Atualizar

É provavelmente possível usar a reflexão para percorrer recursivamente o gráfico do objeto para fazer uma cópia profunda. O WCF usa essa técnica para serializar um objeto, incluindo todos os seus filhos. O truque é anotar todos os objetos filhos com um atributo que os torne detectáveis. Você pode perder alguns benefícios de desempenho, no entanto.

Atualizar

Cotação em teste de velocidade independente (veja comentários abaixo):

Eu executei meu próprio teste de velocidade usando o método de extensão serializar / desserializar de Neil, o método de extensão com base em reflexão de Alex Burtsev do Contango e o AutoMapper, 1 milhão de vezes cada. Serializar-desserializar foi mais lento, levando 15,7 segundos. Então veio o AutoMapper, levando 10,1 segundos. Muito mais rápido foi o método baseado em reflexão que levou 2,4 segundos. De longe, o mais rápido foi o Nested MemberwiseClone, levando 0,1 segundos. Vem para o desempenho versus o incômodo de adicionar código a cada classe para cloná-lo. Se o desempenho não é um problema, use o método de Alex Burtsev. - Simon Tewsi



A documentação do MSDN parece sugerir que o Clone deve executar uma cópia profunda, mas nunca é explicitamente declarado:

A interface ICloneable contém um membro, Clone, que é destinado a oferecer suporte à clonagem além do fornecido pelo MemberWiseClone… O método MemberwiseClone cria uma cópia superficial…

Você pode achar minha postagem útil.

http://pragmaticcoding.com/index.php/cloning-objects-in-c/


A melhor maneira é:

    public interface IDeepClonable<T> where T : class
    {
        T DeepClone();
    }

    public class MyObj : IDeepClonable<MyObj>
    {
        public MyObj Clone()
        {
            var myObj = new MyObj();
            myObj._field1 = _field1;//value type
            myObj._field2 = _field2;//value type
            myObj._field3 = _field3;//value type

            if (_child != null)
            {
                myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same
            }

            int len = _array.Length;
            myObj._array = new MyObj[len]; // array / collection
            for (int i = 0; i < len; i++)
            {
                myObj._array[i] = _array[i];
            }

            return myObj;
        }

        private bool _field1;
        public bool Field1
        {
            get { return _field1; }
            set { _field1 = value; }
        }

        private int _field2;
        public int Property2
        {
            get { return _field2; }
            set { _field2 = value; }
        }

        private string _field3;
        public string Property3
        {
            get { return _field3; }
            set { _field3 = value; }
        }

        private MyObj _child;
        private MyObj Child
        {
            get { return _child; }
            set { _child = value; }
        }

        private MyObj[] _array = new MyObj[4];
    }

Você pode tentar isso

    public static object DeepCopy(object obj)
    {
        if (obj == null)
            return null;
        Type type = obj.GetType();

        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(DeepCopy(array.GetValue(i)), i);
            }
            return Convert.ChangeType(copied, obj.GetType());
        }
        else if (type.IsClass)
        {

            object toret = Activator.CreateInstance(obj.GetType());
            FieldInfo[] fields = type.GetFields(BindingFlags.Public |
                        BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                object fieldValue = field.GetValue(obj);
                if (fieldValue == null)
                    continue;
                field.SetValue(toret, DeepCopy(fieldValue));
            }
            return toret;
        }
        else
            throw new ArgumentException("Unknown type");
    }

Graças ao article DetoX83 no projeto de código.


Eu tenho uma ideia mais simples. Use o LINQ com uma nova seleção.

public class Fruit
{
  public string Name {get; set;}
  public int SeedCount {get; set;}
}

void SomeMethod()
{
  List<Fruit> originalFruits = new List<Fruit>();
  originalFruits.Add(new Fruit {Name="Apple", SeedCount=10});
  originalFruits.Add(new Fruit {Name="Banana", SeedCount=0});

  //Deep Copy
  List<Fruit> deepCopiedFruits = from f in originalFruits
              select new Fruit {Name=f.Name, SeedCount=f.SeedCount};
}






clone