viernes, 20 de enero de 2017

Cloning Objects in .NET Framework Part II




I have decided to do a second chapter of cloning objects in .NET, because in the first chapter I did not include ‘Reflection Mode’ and ‘Expression Tree Mode’ I Know recently, thanks to any comments in my first article.

This two modes of cloning, I believe are very complicated for explain in an article of similar characteristics and we should not try to reinvented the wheel, I have found two fantastic projects Opend Source in Jit Hub and Nuget for we need to get the job done comfortably: Nuclex and CloneExtensions.

The cloning methods of Nuclex and CloneExtensios are strongly types.









Example class


This is a variant of my Customer  and Address classes.


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 it’s a very comprehensive cloning library. It has two cloning modes, for Reflection and Expressions Trees. It has deep and shallow copy with extensions method possibility. Including Field or Property Copy too.

INTALATION

We will install from Nuget:





Reference view:




Example for Reflection type:


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


Example for ExpressionTrees type:


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



It is quite simple, and it has many possibilities.




CloneExtensions

Clone Extensions is a very good cloning library. It is a faster solution, because it is based in Expressions Trees. According to their documentation, the first execution for type is slower, because it uses a reflection part, but in practice is a very fast method.

INSTALATION

We will install from Nuget:




Reference view:




Example of 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);
        }
    }
}


It’s another solution very fast and very easy.



Conclusion

If you need clone some object and if you don’t need an explicit copy, these two libraries are your answer. You don’t try to reinvent the wheel and make easy and nice with these fantastic packages.



Congratulations

Congratulations to Marcin Juraszek (Nuclex) and Jun Wei Lee (CloneExtensions) for your GREAT JOB.