对象(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)深度克隆的主要内容,如果未能解决你的问题,请参考以下文章

关于JavaScript对象深度克隆

Stackoverflow热门问题

js深度克隆对象

JS的深度克隆,利用构造函数原型深度克隆

43.对象深度克隆

26.JavaScript实现对象混合与对象浅度克隆和对象的深度克隆