C#中的浅拷贝和深拷贝
前言
在之前的文章中,我们提及了C#中的值类型和引用类型,同时也简单提到了C#中的拷贝,今天让我们来深入看看这个话题。
啥叫拷贝?
拷贝又名复制,在计算机里面,拷贝的意思就是按照字节一一复制出一个全新的、和现有对象一模一样的对象,在C#中,拷贝有浅拷贝和深拷贝的区别。
浅拷贝
浅拷贝英文叫shallow copy,浅拷贝顾名思义,仅仅会复制很浅的一层,具体来说
- 对于值类型对象,会按位复制出一个全新的对象。
int i = 10;
int j = i;
- 对于含有引用类型对象的值类型对象,值类型对象会按位复制引用类型对象的变量,但是新的引用类型变量会指向与之前老的引用类型对象变量所指向的相同对象。在下面的例子中,Name和Alias都是引用类型对象,所以它们在浅拷贝之后指向的是原来的Name和Alias指向的对象。
struct PersonStruct
{
public string Name { get; set; }
public List<string> Alias { get; set; }
public int Age { get; set; }
}
PersonStruct p1 = new PersonStruct() { Alias = new List<string>(), Name = "Hello" };
PersonStruct p2 = p1;
- 对于引用类型对象,会按位复制出一个全新的引用变量,但是这个引用变量会指向当前引用类型对象。注意,对于引用类型对象,不能简单使用=实现浅拷贝,需要使用Object自带的MemberwiseClone。
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public List<string> Alias { get; set; }
public Person Clone()
{
return (Person)this.MemberwiseClone();
}
}
Person p1 = new Person() { Name = "Deatharthas", Age = 10, Alias = new List<string>() };
Person p2 = p1.Clone();
看起来还不错,对吧?但是请注意,拷贝所产生的新对象中,Name和Alias与之前的对象中的Name和Alias指向同一个引用类型对象,Name是不可修改(Immutable)的String类型,危害还不算太大,但是Alias是一个List,意味着我们不论是往新对象还是老对象里面的Alias加入或者删除某个对象,另外一个对象都会被影响,要想摆脱这种情况,我们就需要深拷贝。
深拷贝
深拷贝又叫Deep Copy,顾名思义,深拷贝完全复制一个对象(不论是引用类型对象还是值类型对象),对其中包含的引用类型对象也会创造一个全新的对象,新产生的对象和老对象完全没有关系,不用担心会相互影响。
遗憾的是,C#没有提供内置的支持深拷贝的函数,唯一一个MemberwiseClone实现的是浅拷贝,要实现深拷贝,需要我们自己想办法。
通过序列化实现深拷贝
Serializable这个属性大家都不陌生,标记为这个属性的类可以被序列化,通过序列化和反序列化,我们可以实现深拷贝。
[Serializable]
class UsingSerilizable
{
public string Name { get; set; }
public List<string> Alias { get; set; }
}
UsingSerilizable u1 = new UsingSerilizable() {Name = "Deatharthas", Alias = new List<string>()};
using (var ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, u1);
ms.Seek(0, SeekOrigin.Begin);
var u2 = (UsingSerilizable)bf.Deserialize(ms);
}
这样就实现了深拷贝,但是这种方法有一个缺点,如果对象包含的属性中,包含有不可序列化的类型,那么这个调用就会抛异常。我们试着修改上面的UsingSerilizable类。
class Empty
{
}
[Serializable]
class UsingSerilizable
{
public string Name { get; set; }
public List<string> Alias { get; set; }
public Empty EmptyProperty { get; set; }
}
再次运行刚刚的代码,得到的结果如下。
所以这个方法并不完美。
不过这个方法给了我们启示,通过序列化再反序列化生成新对象的方式。按照这个思路,那么我们是不是可以借助Json呢?
使用Json实现的深拷贝
首先,我们引入大名鼎鼎的Newton.Json.dll。
接着,搞出一个实现Deep Copy的泛型扩展方法,以方便不同类型使用。
public static class ExtentionForDeepCopy
{
public static T DeepCopy<T>(this T original)
{
return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(original), original.GetType());
}
}
将上面拷贝部分改为
UsingSerilizable u1 = new UsingSerilizable() {Name = "h", Alias = new List<string>() {"a", "b"}, EmptyProperty= new Empty()};
var u2 = u1.DeepCopy();
测试成功,得到我们要的效果。通过Json来实现深拷贝是可行的,并且没有像之前那种方法一样需要序列化等标记。
总结
C#里面的拷贝分为浅拷贝和深拷贝。
- 值类型通过=实现浅拷贝
- 引用类型通过MemberwiseClone实现浅拷贝
- 没有内置的支持深拷贝的函数,但是我们可以借助Newton.Json.dll来实现