提供深度克隆对象功能,基于编译表达式实现,性能与原生代码几无差别,远超 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也会进行克隆吗?
会。