浅拷贝:给对象拷贝一份新的对象。定义——只对值类型(或 string)类型分配新的内存地址。
深拷贝:给对象拷贝一份全新的对象。定义——对值类型分配新的内存地址,引用类型、以及引用类型的内部字段分配的新的地址。
觉得这样有点绕口了。
我是这么定义的:浅拷贝,换汤不换药。
问题起源:
昨天被同事问到一个浅拷贝与深拷贝区别的问题,说实在的,记得在学校时在书在看过相关概念区别。只是,那时的在校生,又有几个能对书本上那写的尽量让鬼都看不懂知识能清晰的理解呢。
工作后虽然也有用到Clone相关的内容,不过也仅是应用,基础的概念还是没去仔细的理解,以于后来DataTable内部那不深不浅的架构拷贝都混和这些概念混在一起了。
曾经的以为:
让得一年多以前,我去面试,被一个人问到浅拷贝与深拷贝区别,我答:浅拷贝就是拷贝就是拷贝架构,不包括值,深拷贝就是连值也一同拷贝。当我答出这样错误的答案时,对方没有纠正,反而似乎对我的答案还挺满意的。
唉,面试管自己不懂还问我这问题,还让我一直以为到现在都是这个样。
同事的质问:
接着同事说它的示例里,发现值是有拷贝的,于是我意识到我的认知是有问题了,我重新百度了一下。网上乱七杂八的答案,让我得一个模糊的似乎正确的答案:浅拷贝不拷贝内部类的对象
引申出的错乱:
接着同事的疑问更一度把我引向这个容易迷乱的错误深渊:浅拷贝是对引用类型拷贝地址,对值类型直接进行拷贝。
最后,为了解惑,还是用示例来说话了:
class DemoClass : ICloneable{
public int intValue = 1;
public string strValue = "1";
public PersonEnum pEnum = PersonEnum.EnumA;
public PersonStruct pStruct = new PersonStruct();
public Person pClass = new Person("1"); public int[] pIntArray = new int[] { 1 };
public string[] pArray = new string[] { "1" };
#region ICloneable 成员
public DemoClass()
{
pStruct.StructValue = 1;
}
public object Clone()
{
return this.MemberwiseClone();
}
#endregion
}
class Person
{
public string Name;
public Person(string name)
{
Name = name;
}
}
public enum PersonEnum
{
EnumA=1,
EnumB=2
}
public struct PersonStruct
{
public int StructValue;
}
说明:
这里的示例,我用上了:int
stringint[]
string[]
enum
struct
class
然后接下来会产生实例A和克隆实例B。
接着改变B的值,看A的值会不会被改变。
接下我们看main方法
static void Main(string[] args){
Demo();
}
public static void Demo()
{
DemoClass A = new DemoClass();
DemoClass B = (DemoClass)A.Clone();
B.intValue = 2;
Write(string.Format(" int->[A:{0}] [B:{1}]", A.intValue, B.intValue));
B.strValue = "2";
Write(string.Format(" string->[A:{0}] [B:{1}]", A.strValue, B.strValue));
B.pEnum = PersonEnum.EnumB;
Write(string.Format(" Enum->[A:{0}] [B:{1}]", (int)A.pEnum, (int)B.pEnum));
B.pStruct.StructValue = 2;
Write(string.Format(" struct->[A:{0}] [B:{1}]", A.pStruct.StructValue, B.pStruct.StructValue));
B.pIntArray[0] = 2;
Write(string.Format(" intArray->[A:{0}] [B:{1}]", A.pIntArray[0], B.pIntArray[0]));
B.pStringArray[0] = "2";
Write(string.Format("stringArray->[A:{0}] [B:{1}]", A.pStringArray[0], B.pStringArray[0]));
B.pClass.Name = "2";
Write(string.Format(" Class->[A:{0}] [B:{1}]", A.pClass.Name, B.pClass.Name));
System.Console.Read();
}
static void Write(string msg)
{
System.Console.WriteLine(msg);
}
说明:
我们通过改变B实例的值,然后打印出A和B的值看结果。
打印结果如下:
从最后输出的结果我们得知:
对于内部的Class的对象和数组,会Copy地址一份。[从而改变B时,A也被改变了] 而对于其它内置的int/string/Enum/struct/object类型,则进行值copy。
最后,我试着百度寻找浅拷贝最原生的定义,浅搜了一下找不到,那这个定义,就留给读者解答一下了。
接着,我顺手比较了一下浅拷贝和深拷贝的性能测试:
先在DemoClass加入一个函数:
public object CloneNew(){
return new DemoClass();
}
接着我们写示例代码比较:
public static void Compare(){
DemoClass baseClass = new DemoClass();
DateTime start = DateTime.Now;
for (int i = 0; i < 1000000; i++)
{
DemoClass newClass = (DemoClass)baseClass.Clone();
}
TimeSpan ts = DateTime.Now - start;
System.Console.WriteLine("浅拷贝:" + ts.Ticks);
DateTime start2 = DateTime.Now;
for (int j = 0; j < 1000000; j++)
{
DemoClass newClass = (DemoClass)baseClass.CloneNew();
}
TimeSpan ts2 = DateTime.Now - start2;
System.Console.WriteLine("深拷贝:" + ts2.Ticks);
System.Console.Read();
}
最后得出结果:
看来直接用浅拷贝性能还不如直接返回一个new的对象。同样的,用克隆返回object最后还得对类型转换进行一些开销,教人情何以堪!
附言:
这两天刚好感冒,插了这两篇解惑篇,都是临时插进来的的问题了,接下来会继续写 CYQ.Data 轻量数据层之路 框架 的相关文章。