65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET Framework 中的对象克隆 - 第二部分

2017年1月21日

CPOL

2分钟阅读

viewsIcon

16154

继续讨论 .NET Framework 中的对象克隆。

引言

我决定撰写关于 .NET 中对象克隆的第二篇文章,因为在第一篇文章中,我没有包含“反射模式”和“表达式树模式”。这要归功于我第一篇文章中的一些评论。

我认为这两种克隆模式对于在类似的文章中解释来说非常复杂,我们不应该试图重复造轮子。我在 Jit Hub 和 Nuget 中找到了两个很棒的 *开源* 项目,可以让我们轻松完成这项工作:NuclexCloneExtensions

NuclexCloneExtensions 的克隆方法都是强类型的。

这是我文章的第一部分.

示例类

这些是我们文章中使用的示例类,CustomerAddress

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 是一个非常全面的克隆库。它具有用于反射表达式树的两种克隆模式。它具有深度和浅拷贝,以及扩展方法的功能。包括字段或属性拷贝。

安装

我们将从 Nuget 安装

参考视图

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

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

它非常简单,并且具有许多可能性。

CloneExtensions

Clone Extensions 是一个非常好的克隆库。它是一个更快的解决方案,因为它基于表达式树。根据他们的文档,首次执行类型时会比较慢,因为它使用了反射部分,但在实践中,它是一种非常快速的方法。

安装

我们将从 Nuget 安装

参考视图

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

这是另一个非常快速且非常简单的解决方案。

结论

如果您需要克隆某个对象,并且不需要显式复制,那么这两个库就是您的答案。不要试图重复造轮子,使用这些出色的软件包让一切变得简单而美好。

恭喜

恭喜 Marcin Juraszek (Nuclex) 和 Jun Wei Lee (CloneExtensions) 你们的杰出工作!

© . All rights reserved.