Cuando publiqué la primera parte del artículo, decidí dejar fuera un modo más de clonación, el impulsado por Reflection. La decisión de dejarlo fuera vino principalmente porque hubiera querido abordarlo profundamente y pensaba que se podía alargar mucho y perder el sentido. A la vez en los comentarios de otras páginas especializadas me sugirieron también el modo mediante Expressions Trees, algo que no conocía en este planteamiento, pero tratándose de Arboles de Expresiones, no era para nada lo que se puede decir sencillo.
Buscando más información, me topé con 2 fantásticas
librerías en Git Hub, accesibles
mediante Nuget, que
funcionaban perfectamente bien y que hacen el trabajo de forma maravillosa, así
que ¿para qué reinventar la rueda?
En esta segunda parte trataré de explicar ejemplos con Nuclex y CloneExtensions, que nos permiten realizar clonados profundo,
tanto en modo Reflection como Expression Trees.
Clase de Ejemplo.
Esta es una variante de las clases Customer y Address.
public class Customer { public int ID { get; set; } public string Name { get; set; } public decimal Sales { get; set; } public DateTime EntryDate { get; set; } public Address Adress { get; set; } public Collection<string> Mails { get; set; } public List<Address> Adresses { get; set; } protected string Data1 { get; set; } private string Data2 { get; set; } public string ReadOnlyField { get; set; } public Customer() { Data1 = "data1"; Data2 = "Data2"; ReadOnlyField = "readonly_data"; } } public class Address { public string Street { get; set; } public string City { get; set; } public int ZipCode { get; set; } }
Nuclex
Nuclex.Cloning es una librería muy completa y sencilla de
usar con capacidad de realizar clonado mediante Reflection y ExpressionTrees.
También permite realizar clonado superficial o en profundidad y
copiados a nivel de propiedad o campo.
INSTALACIÓN
La instalaremos a nivel de Nuget:
Vista de las referencias:
Ejemplo de tipo Reflection:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Nuclex.Cloning; using CloneLib; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Clone.Tests { [TestClass] public class NuclexReflectionTests { [TestMethod] public void NuclexReflectorClone() { var customer1 = new Customer { ID = 1, Name = "Test", EntryDate = DateTime.Today, Sales = 1000m, Adress = new Address { Street = "One street", City = "City", ZipCode = 2222 }, Mails = new Collection<string>() { "a@b.com", "b@c.com" }, Adresses = new List<Address> { new Address { City = "aaa", Street = "bbb", ZipCode = 111 }, new Address { City = "ddd", Street = "eee", ZipCode = 222 } } }; var cloneCustomer = ReflectionCloner.DeepFieldClone<Customer>(customer1); /// ****** We have other posibilities, for diferents clone depths //var cloneCustomer = ReflectionCloner.DeepPropertyClone (customer1); //var cloneCustomer = ReflectionCloner.ShallowFieldClone (customer1); //var cloneCustomer = ReflectionCloner.ShallowPropertyClone (customer1); cloneCustomer.Adress.City = "New city"; Assert.AreNotEqual(customer1, cloneCustomer); Assert.AreEqual(customer1.ID , cloneCustomer.ID); Assert.AreEqual(customer1.Name , cloneCustomer.Name); Assert.AreEqual(customer1.EntryDate, cloneCustomer.EntryDate); Assert.AreEqual(customer1.Sales , cloneCustomer.Sales); Assert.AreEqual(customer1.Mails[0], cloneCustomer.Mails[0]); Assert.AreEqual(customer1.Mails[1], cloneCustomer.Mails[1]); Assert.AreEqual(customer1.Adresses[0].City , cloneCustomer.Adresses[0].City); Assert.AreEqual(customer1.Adresses[0].Street , cloneCustomer.Adresses[0].Street); Assert.AreEqual(customer1.Adresses[0].ZipCode, cloneCustomer.Adresses[0].ZipCode); Assert.AreEqual(customer1.Adresses[1].City , cloneCustomer.Adresses[1].City); Assert.AreEqual(customer1.Adresses[1].Street , cloneCustomer.Adresses[1].Street); Assert.AreEqual(customer1.Adresses[1].ZipCode, cloneCustomer.Adresses[1].ZipCode); Assert.AreEqual(customer1.ReadOnlyField, cloneCustomer.ReadOnlyField); /// Change values in clone object for validate objects don't linked cloneCustomer.Adress.City = "Changed City"; Assert.AreNotEqual(customer1.Adress.City, cloneCustomer.Adress.City); cloneCustomer.Mails[0] = "mailchanged@aa.com"; Assert.AreNotEqual(customer1.Mails[0], cloneCustomer.Mails[0]); cloneCustomer.Adresses[0].Street = "New Street"; Assert.AreNotEqual(customer1.Adresses[0].Street, cloneCustomer.Adresses[0].Street); } } }
Ejemplo de tipo ExpressionTrees:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Nuclex.Cloning; using CloneLib; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Clone.Tests { [TestClass] public class NeclexExpressionTreeClonerText { [TestMethod] public void NuclexReflectorClone() { var customer1 = new Customer { ID = 1, Name = "Test", EntryDate = DateTime.Today, Sales = 1000m, Adress = new Address { Street = "One street", City = "City", ZipCode = 2222 }, Mails = new Collection<string>() { "a@b.com", "b@c.com" }, Adresses = new List<Address> { new Address { City = "aaa", Street = "bbb", ZipCode = 111 }, new Address { City = "ddd", Street = "eee", ZipCode = 222 } } }; var cloneCustomer = ExpressionTreeCloner.DeepFieldClone<Customer>(customer1); /// ****** We have other posibilities, for diferents clone depths //var cloneCustomer = ExpressionTreeCloner.DeepPropertyClone (customer1); //var cloneCustomer = ExpressionTreeCloner.ShallowFieldClone (customer1); //var cloneCustomer = ExpressionTreeCloner.ShallowPropertyClone (customer1); cloneCustomer.Adress.City = "New city"; Assert.AreNotEqual(customer1, cloneCustomer); Assert.AreEqual(customer1.ID , cloneCustomer.ID); Assert.AreEqual(customer1.Name , cloneCustomer.Name); Assert.AreEqual(customer1.EntryDate, cloneCustomer.EntryDate); Assert.AreEqual(customer1.Sales , cloneCustomer.Sales); Assert.AreEqual(customer1.Mails[0], cloneCustomer.Mails[0]); Assert.AreEqual(customer1.Mails[1], cloneCustomer.Mails[1]); Assert.AreEqual(customer1.Adresses[0].City , cloneCustomer.Adresses[0].City); Assert.AreEqual(customer1.Adresses[0].Street , cloneCustomer.Adresses[0].Street); Assert.AreEqual(customer1.Adresses[0].ZipCode, cloneCustomer.Adresses[0].ZipCode); Assert.AreEqual(customer1.Adresses[1].City , cloneCustomer.Adresses[1].City); Assert.AreEqual(customer1.Adresses[1].Street , cloneCustomer.Adresses[1].Street); Assert.AreEqual(customer1.Adresses[1].ZipCode, cloneCustomer.Adresses[1].ZipCode); Assert.AreEqual(customer1.ReadOnlyField, cloneCustomer.ReadOnlyField); /// Change values in clone object for validate objects don't linked cloneCustomer.Adress.City = "Changed City"; Assert.AreNotEqual(customer1.Adress.City, cloneCustomer.Adress.City); cloneCustomer.Mails[0] = "mailchanged@aa.com"; Assert.AreNotEqual(customer1.Mails[0], cloneCustomer.Mails[0]); cloneCustomer.Adresses[0].Street = "New Street"; Assert.AreNotEqual(customer1.Adresses[0].Street, cloneCustomer.Adresses[0].Street); } } }
Muy fácil, muy sencillo y con un millón de posiblidades.
CloneExtensions
CloneExtensions es una librería de uso extremadamente
simple, muy rápido ya que está basada en Expressions Trees. Según la documentación
del autor el primer clonado por tipo es un poco más lento ya que tiene recorrer
las propiedades con Reflection, pero las siguientes ya son inmediatas. En la
práctica va como un tiro, no se aprecia prácticamente.
INSTALACIÓN
La instalaremos mediante Nuget:
Vista de las referencias:
Ejemplo de CloneExtensions:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using CloneLib; using System.Collections.ObjectModel; using System.Collections.Generic; using CloneExtensions; namespace Clone.Tests { [TestClass] public class CloneExtensionsTests { [TestMethod] public void NuclexReflectorClone() { var customer1 = new Customer { ID = 1, Name = "Test", EntryDate = DateTime.Today, Sales = 1000m, Adress = new Address { Street = "One street", City = "City", ZipCode = 2222 }, Mails = new Collection<string>() { "a@b.com", "b@c.com" }, Adresses = new List<Address> { new Address { City = "aaa", Street = "bbb", ZipCode = 111 }, new Address { City = "ddd", Street = "eee", ZipCode = 222 } } }; var cloneCustomer = customer1.GetClone(); cloneCustomer.Adress.City = "New city"; Assert.AreNotEqual(customer1, cloneCustomer); Assert.AreEqual(customer1.ID , cloneCustomer.ID); Assert.AreEqual(customer1.Name , cloneCustomer.Name); Assert.AreEqual(customer1.EntryDate , cloneCustomer.EntryDate); Assert.AreEqual(customer1.Sales , cloneCustomer.Sales); Assert.AreEqual(customer1.Mails[0], cloneCustomer.Mails[0]); Assert.AreEqual(customer1.Mails[1], cloneCustomer.Mails[1]); Assert.AreEqual(customer1.Adresses[0].City , cloneCustomer.Adresses[0].City); Assert.AreEqual(customer1.Adresses[0].Street , cloneCustomer.Adresses[0].Street); Assert.AreEqual(customer1.Adresses[0].ZipCode, cloneCustomer.Adresses[0].ZipCode); Assert.AreEqual(customer1.Adresses[1].City , cloneCustomer.Adresses[1].City); Assert.AreEqual(customer1.Adresses[1].Street , cloneCustomer.Adresses[1].Street); Assert.AreEqual(customer1.Adresses[1].ZipCode, cloneCustomer.Adresses[1].ZipCode); Assert.AreEqual(customer1.ReadOnlyField, cloneCustomer.ReadOnlyField); /// Change values in clone object for validate objects don't linked cloneCustomer.Adress.City = "Changed City"; Assert.AreNotEqual(customer1.Adress.City, cloneCustomer.Adress.City); cloneCustomer.Mails[0] = "mailchanged@aa.com"; Assert.AreNotEqual(customer1.Mails[0], cloneCustomer.Mails[0]); cloneCustomer.Adresses[0].Street = "New Street"; Assert.AreNotEqual(customer1.Adresses[0].Street, cloneCustomer.Adresses[0].Street); } } }
De Nuevo otra solución, fácil, rápida y sencilla.
Conclusión
Si tú lo que necesitas es hacer un clonado sencillo y sin
partes demasiado especiales, cualquiera de estas dos librerías son tu solución.
En caso de necesitar clonar algún tipo con una determinada estructura, yo optaría
por los métodos más tradicionales de la primera
parte del artículo.
Felicitaciones
Felicitaciones a Marcin Juraszek (Nuclex) y
a Jun Wei
Lee (CloneExtensions) por su EXTRAORDINARIO
TRABAJO.
No hay comentarios :
Publicar un comentario