对象(poco)深度克隆
Posted dotNET跨平台
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对象(poco)深度克隆相关的知识,希望对你有一定的参考价值。
提供深度克隆对象功能,基于编译表达式实现,性能与原生代码几无差别,远超 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也会进行克隆吗?
会。
以上是关于对象(poco)深度克隆的主要内容,如果未能解决你的问题,请参考以下文章