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





5.00/5 (6投票s)
继续讨论 .NET Framework 中的对象克隆。
引言
我决定撰写关于 .NET 中对象克隆的第二篇文章,因为在第一篇文章中,我没有包含“反射模式”和“表达式树模式”。这要归功于我第一篇文章中的一些评论。
我认为这两种克隆模式对于在类似的文章中解释来说非常复杂,我们不应该试图重复造轮子。我在 Jit Hub 和 Nuget 中找到了两个很棒的 *开源* 项目,可以让我们轻松完成这项工作:Nuclex 和 CloneExtensions。
Nuclex 和 CloneExtensions 的克隆方法都是强类型的。
示例类
这些是我们文章中使用的示例类,Customer
和 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
是一个非常全面的克隆库。它具有用于反射和表达式树的两种克隆模式。它具有深度和浅拷贝,以及扩展方法的功能。包括字段或属性拷贝。
安装
我们将从 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) 你们的杰出工作!