Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Cloning Objects in .NET Framework - Part II

11 Apr 2017CPOL2 min read 15.8K  
Continues with the cloning objects .NET Framework

Introduction

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.

These two modes of cloning, I believe, are very complicated for explaining 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 CloneExtensions are strongly typed.

This is the first part of my article.

Example Classes

These are the example classes we use in our article, Customer and Address.

C#
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 is 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.

INTALLATION

We will install from Nuget:

Image 1

Reference view:

Image 2

Example for Reflection type:

C#
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 possibilities, for different 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:

C#
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.

INSTALLATION

We will install from Nuget:

Image 3

Reference view:

Image 4

Example of CloneExtensions:

C#
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 that is very fast and very easy.

Conclusion

If you need to 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 it easy and nice with these fantastic packages.

Congratulations

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)