提供深度克隆对象功能,基于编译表达式实现,性能与原生代码几无差别,远超 json/binary 序列化实现。.
1. 简单示例
class Person { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public DateTime Birth { get; set; } public double Score { get; set; } public DateTime CreateTime { get; set; } public DateTime UpdateTime { get; set; } public EnumState State { get; set; } public string Desc { get; set; } public string Phone { get; set; } } //克隆 var list = new List<Person>(){/*放点数据*/} var newList = list.DeepClone();
2. 性能
分别与原生代码,json、binary 序列化机制比对;
原生代码如:
var newList = list.Select(i => new Person { Id = i.Id/*其他属性*/}).ToList();
```
json序列化如:
var newList = JsonConvert.DeserializeObject<List<Person>>(JsonConvert.SerializeObject(list));binary序列化如:
BinaryFormatter bf = new BinaryFormatter(); var stream = new MemoryStream(); bf.Serialize(stream, list); stream.Seek(0, SeekOrigin.Begin);bf.Deserialize(stream);
测试效果如下:
测试代码,参考:https://gitee.com/jackletter/DotNetCommon/blob/master/tests/DeepClonePerformanceTest/Program.cs
3. 详细功能
单元测试地址:https://gitee.com/jackletter/DotNetCommon/blob/master/tests/DotNetCommon.Test/Extensions/ObjectTests_DeepClone.cs
3.1 支持的完整数据类型如下:
-
基础类型 sbyte/byte/short/ushort/int/uint/long/ulong/float/double/decimal bool/enum/char/string
DateTime/DateTimeOffset/DateOnly/TimeOnly/TimeSpan/Guid
-
pojo、结构体
-
数组、集合、字典
T[]
、List<T>
、Dictionary<TKey, TValue>
、HashSet<T>
、LinkedList<T>
、ReadOnlyCollection<T>
注意:必须是泛型的且指定具体的类型,而不是
List<object>
-
元组
Tuple<T1,...
、ValueTuple<T1....
-
匿名类型
new {Id=1.Name="小明",Teacher=new Teacher()}.DeepClone()
-
JObject/JArray/JToken
-
已实现 ICloneable 接口的类型
3.2 特点
该克隆方法支持引用关系的拷贝,如:
class Node { public int Id { get; set; } public Node Parent { get; set; } public List<Node> Children { get; set; } } //构造 var node=new Node{ Id = 1, Childrem = new List<Node>()}; var subNode=new Node{ Id = 2, Parent = node }; node.Children.Add(subNode); //深度克隆,不会死循环,引用关系会一并拷贝过来 var newNode = node.DeepClone(); Assert.IsTrue(newNode != node); Assert.IsTrue(newNode.Children[0].Parent == newNode);
之所以能将引用关系也拷贝过来,是因为内部使用了字典进行缓存,如果明确实例内部没有引用关系的话,可以将它关闭,关闭后性能提升将近一倍。
//关闭克隆时的引用关系 node.DeepClone(false);
4. FAQ
4.1 为什么会支持元组的克隆,元组不是值类型吗?
元组确实是值类型,但里面可以存放 对象引用,如:
(int Id, Teacher teacher) tuple = (1,new Teacher{ Name = "小明"}); var newTuple = tuple.DeepClone(false); newTuple.teacher.Name+="update"; //由于是深拷贝,旧数据并未更改 Assert.IsTrue(tuple.teacher.Name == "小明");
4.2 为什么会支持匿名类型的克隆,匿名类型不是只读的吗?
这个和元组就相似了,虽然匿名类型是只读的,但它里面可以存放对象引用,如:
var obj = new { Id = 1, teacher = new Teacher{ Name = "小明"}}; var newObj = obj.DeepClone(false); newObj.teacher.Name+="update";
4.3 为什么会支持 ReadOnlyCollection 这不是只读的吗?
一方面,虽然 ReadOnlyCollection 本身只读,但它里面存的对象实例属性是可更改,肯定要拷贝;
另一方面,ReadOnlyCollection 只是对外暴露的接口只读,但没有说它里面的数据集一定不能改,如:
var list = new List<int>{ 1, 2 }; var readList = new ReadOnlyCollection<int>(list); //readList 此时是只读的,但仍然可以更改 list list.Add(3); //readList 也随之被更改 Assert.IsTrue(readList.Count == 3);
4.4 为什么List、Array 都必须是泛型且指定具体的类型?
这是因为,克隆的逻辑是基于编译表达式实现的,相当于在运行时 生成一个函数,在生成这个函数时会分析 List<T>
中的T
,
如果T
是 Person{ int Id,string Name} 那么生成的函数就是 old=>new Person(){Id=old.Id,Name=old.Name}。
如果是非泛型的 List 或者是 List<object>
那么将不能反射到具体的属性,也就不能生成对应的函数。
4.5 字典 Dictionary<TKey, TValue> 的TKey也会进行克隆吗?
会。