提供深度克隆對象功能,基于編譯表達式實現,性能與原生代碼幾無差別,遠超 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也會進行克隆嗎?
會。