首先感谢各位点进这个帖子,下来说说我所遇到的问题,下午没事研究浅拷贝和深拷贝
Object.MemberwiseClone 方法
创建当前 Object 的浅表副本。 命名空间:System
程序集:mscorlib(在 mscorlib.dll 中)语法
protected Object MemberwiseClone ()返回值
当前 Object 的浅表副本。
备注
--------------------------------------------------------------------------------MemberwiseClone 方法创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。例如,考虑一个名为 X 的对象,该对象引用对象 A 和 B。对象 B 又引用对象 C。X 的浅表副本创建一个新对象 X2,该对象也引用对象 A 和 B。与此相对照,X 的深层副本创建一个新对象 X2,该对象引用新对象 A2 和 B2,它们分别是 A 和 B 的副本。B2 又引用新对象 C2,C2 是 C 的副本。使用实现 ICloneable 接口的类执行对象的浅表或深层复制。 于是我就想试验一下,结果遇到下面的情况:
Code1.using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;namespace Object_Clone_Wars
{
public class Name
{
private string _PName; public string PName
{
get { return _PName; }
set { _PName = value; }
}
}
public class Charon : ICloneable
{ private int age; public int Age
{
get { return age; }
set { age = value; }
}
private Name name = new Name();
public string Name
{
get { return name.PName; }
set { name.PName = value; }
}
public void ShowCharonInfo()
{
Console.WriteLine("----------------------------");
Console.WriteLine("Name:{0},Age:{1}", this.Name, this.Age);
Console.WriteLine("----------------------------");
}
#region ICloneable 成员 public object Clone()
{
return this.MemberwiseClone();
} #endregion
}
class Program
{ static void Main(string[] args)
{
Console.WriteLine("原对象");
Charon charon = new Charon();
charon.Name = "王明";
charon.Age = 18;
charon.ShowCharonInfo();
Console.WriteLine("修改后的对象");
Charon copy = (Charon)charon.Clone();
charon.Name = "王梦想";
charon.Age = 20;
charon.ShowCharonInfo();
Console.WriteLine("修改后的浅拷贝对象");
copy.ShowCharonInfo(); }
}
}输出为:
原对象
----------------------------
Name:王明,Age:18
----------------------------
修改后的对象
----------------------------
Name:王梦想,Age:20
----------------------------
修改后的浅拷贝对象
----------------------------
Name:王梦想,Age:18
----------------------------
Code2.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;namespace Object_Clone_Wars
{
public class Charon : ICloneable
{ private int age; public int Age
{
get { return age; }
set { age = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public void ShowCharonInfo()
{
Console.WriteLine("----------------------------");
Console.WriteLine("Name:{0},Age:{1}", this.Name, this.Age);
Console.WriteLine("----------------------------");
}
#region ICloneable 成员 public object Clone()
{
return this.MemberwiseClone();
} #endregion
}
class Program
{ static void Main(string[] args)
{
Console.WriteLine("原对象");
Charon charon = new Charon();
charon.Name = "王明";
charon.Age = 18;
charon.ShowCharonInfo();
Console.WriteLine("修改后的对象");
Charon copy = (Charon)charon.Clone();
charon.Name = "王梦想";
charon.Age = 20;
charon.ShowCharonInfo();
Console.WriteLine("修改后的浅拷贝对象");
copy.ShowCharonInfo(); }
}
}输出为:
原对象
----------------------------
Name:王明,Age:18
----------------------------
修改后的对象
----------------------------
Name:王梦想,Age:20
----------------------------
修改后的浅拷贝对象
----------------------------
Name:王明,Age:18
----------------------------
到这里,我想不通了,为啥Code1浅拷贝之后Name值是修改后,而Code2浅拷贝之后Name值为修改前.
对于上面说的:如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象。
值类型Age没有问题,那么同样是引用类型的String和Class为什么就有区别.想到了string是特殊的引用类型,它的实例是只读的.但是Class中的Name也是String啊.
我们理一理思路.
1.charon.Name = "王明";的时候,这个时候charon.Name指向"王明",
2.Charon copy = (Charon)charon.Clone();浅拷贝了一下,这个时候copy.Name复制charon.Name值是"王明",
3.charon.Name = "王梦想"; 修改之后,charon.Name指向"王梦想",而copy.Name复制charon.Name,值应该是"王梦想",
想不通了,这期间问一个同学,他输出都答对了,但是也说不上来为什么,还说你知道那些干啥,只要知道结果就行了,我说知其然而不知其所以然跟没学一样,他说那你为啥吃饭的时候,不想一下这个面是如何做的.说我浪费时间浪费青春.最后不欢而散,忘各位解答我的疑问.分不多100敬上.
Object.MemberwiseClone 方法
创建当前 Object 的浅表副本。 命名空间:System
程序集:mscorlib(在 mscorlib.dll 中)语法
protected Object MemberwiseClone ()返回值
当前 Object 的浅表副本。
备注
--------------------------------------------------------------------------------MemberwiseClone 方法创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。例如,考虑一个名为 X 的对象,该对象引用对象 A 和 B。对象 B 又引用对象 C。X 的浅表副本创建一个新对象 X2,该对象也引用对象 A 和 B。与此相对照,X 的深层副本创建一个新对象 X2,该对象引用新对象 A2 和 B2,它们分别是 A 和 B 的副本。B2 又引用新对象 C2,C2 是 C 的副本。使用实现 ICloneable 接口的类执行对象的浅表或深层复制。 于是我就想试验一下,结果遇到下面的情况:
Code1.using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;namespace Object_Clone_Wars
{
public class Name
{
private string _PName; public string PName
{
get { return _PName; }
set { _PName = value; }
}
}
public class Charon : ICloneable
{ private int age; public int Age
{
get { return age; }
set { age = value; }
}
private Name name = new Name();
public string Name
{
get { return name.PName; }
set { name.PName = value; }
}
public void ShowCharonInfo()
{
Console.WriteLine("----------------------------");
Console.WriteLine("Name:{0},Age:{1}", this.Name, this.Age);
Console.WriteLine("----------------------------");
}
#region ICloneable 成员 public object Clone()
{
return this.MemberwiseClone();
} #endregion
}
class Program
{ static void Main(string[] args)
{
Console.WriteLine("原对象");
Charon charon = new Charon();
charon.Name = "王明";
charon.Age = 18;
charon.ShowCharonInfo();
Console.WriteLine("修改后的对象");
Charon copy = (Charon)charon.Clone();
charon.Name = "王梦想";
charon.Age = 20;
charon.ShowCharonInfo();
Console.WriteLine("修改后的浅拷贝对象");
copy.ShowCharonInfo(); }
}
}输出为:
原对象
----------------------------
Name:王明,Age:18
----------------------------
修改后的对象
----------------------------
Name:王梦想,Age:20
----------------------------
修改后的浅拷贝对象
----------------------------
Name:王梦想,Age:18
----------------------------
Code2.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;namespace Object_Clone_Wars
{
public class Charon : ICloneable
{ private int age; public int Age
{
get { return age; }
set { age = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public void ShowCharonInfo()
{
Console.WriteLine("----------------------------");
Console.WriteLine("Name:{0},Age:{1}", this.Name, this.Age);
Console.WriteLine("----------------------------");
}
#region ICloneable 成员 public object Clone()
{
return this.MemberwiseClone();
} #endregion
}
class Program
{ static void Main(string[] args)
{
Console.WriteLine("原对象");
Charon charon = new Charon();
charon.Name = "王明";
charon.Age = 18;
charon.ShowCharonInfo();
Console.WriteLine("修改后的对象");
Charon copy = (Charon)charon.Clone();
charon.Name = "王梦想";
charon.Age = 20;
charon.ShowCharonInfo();
Console.WriteLine("修改后的浅拷贝对象");
copy.ShowCharonInfo(); }
}
}输出为:
原对象
----------------------------
Name:王明,Age:18
----------------------------
修改后的对象
----------------------------
Name:王梦想,Age:20
----------------------------
修改后的浅拷贝对象
----------------------------
Name:王明,Age:18
----------------------------
到这里,我想不通了,为啥Code1浅拷贝之后Name值是修改后,而Code2浅拷贝之后Name值为修改前.
对于上面说的:如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象。
值类型Age没有问题,那么同样是引用类型的String和Class为什么就有区别.想到了string是特殊的引用类型,它的实例是只读的.但是Class中的Name也是String啊.
我们理一理思路.
1.charon.Name = "王明";的时候,这个时候charon.Name指向"王明",
2.Charon copy = (Charon)charon.Clone();浅拷贝了一下,这个时候copy.Name复制charon.Name值是"王明",
3.charon.Name = "王梦想"; 修改之后,charon.Name指向"王梦想",而copy.Name复制charon.Name,值应该是"王梦想",
想不通了,这期间问一个同学,他输出都答对了,但是也说不上来为什么,还说你知道那些干啥,只要知道结果就行了,我说知其然而不知其所以然跟没学一样,他说那你为啥吃饭的时候,不想一下这个面是如何做的.说我浪费时间浪费青春.最后不欢而散,忘各位解答我的疑问.分不多100敬上.
解决方案 »
- C# 想用Timer控件,将数据库中的图片每隔10秒,显示一张出来,请大家帮助
- 想知道下面C++的DLL中一个函数的调用办法,请帮举个C#调用例?
- 请教如梦,一个关联表数据在DataGridView的处理,谢谢!
- 如何把文本文档(.txt)内容变到Excel里面?
- c# 是本程序的窗口始终被选中(关注给分)
- 看不明哪里二维数组错在哪里
- C#发送邮件可以不设置发件人的密码吗
- ********* 搞过 sap 的看过来,乱码问题,焦急在线等!!!! *************
- RawSocket中IPAddress的问题
- 请问那位大侠有用C#写过支持断点传续的经验,链接,代码,请指点,谢谢了
- using 如此用法有什么好处
- 关于自定义的Rectangle控件
在代码1中,你的Name属性是一个引用类型,自定义的class,所以你浅拷贝后,得到的拷贝对象,期中的Name是只有拷贝了引用,所以当他的内容被修改时,你用那个浅拷贝得到的引用,去查看内容,自然是被修改后的名字了,之余int的age,浅拷贝得到的是拷贝当时的值,这个理解起来比较简单。在代码2中,你的Name是一个String类型的字段,要注意Sting在.net中是比较特殊的一个引用类型:
考虑这样的代码
string a="a"
a="b"
这里的a,其实它实际指向的地址已经修改了。系统重新生产了一个b的字符串,然后把b字符串的地址复制给a变量,对应原先的a字符串会在某个时刻给GC回收。所以你在代码2中,对string的拷贝实际上也是一个值的拷贝,我不知道你能否理解我的解释了。
对于您所提出的问题:需要一一解释才行.1.>到这里,我想不通了,为啥Code1浅拷贝之后Name值是修改后,而Code2浅拷贝之后Name值为修改前.
对于上面说的:如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象。
首先解释下为什么code1中Name值为修改后的值:如果字段是引用类型,则复制引用但不复制引用的对象。
根据这句话的含义我们知道.
Charon copy = (Charon)charon.Clone();
此时copy所指向的在堆中分配的对象和charon所指向的在堆中分配的对象是一致的.(也即是说如果修改了charon.Name属性的话,并没有影响copy所指向的对象的引用,所以才有code1所出现的现象);
而在code2中所出现的问题中,可能您没有仔细的想一下,Charon类型中的Name属性只是一个string类型的数据属性而已.虽然copy与charon所指向的堆中的对象是同一引用.但是在修改charon的Name数据属性时,此时
由于此属性为string引用类型致使该对象的Name属性指向了一个新的引用.然而copy依然指向原来的那个Name属性.
深拷贝:不仅复制对象的基本类,同时也复制原对象中的对象.就是说完全是新对象产生的
using System;class DeepCopy : ICloneable
{
public int i;
public DeepCopy()
{
}
// 供Clone方法调用的私有构造函数
private DeepCopy(int i)
{
this.v = (int)i.Clone();
}
public Object Clone()
{
// 构造一个新的DeepCopy对象
return new DeepCopy(this.i);
}
public void Display()
{
Console.Write( i + ", ");
Console.WriteLine();
}
}
看看字符串驻留机制
首先String是引用类型,不过比较特殊,一般情况下都可以当作值类型来使用,
而且其大部分表现也倾向于值类型,这点楼主肯定能理解。其次,Code2的代码楼主肯定没有歧义,或者没有不理解的地方,
感觉混淆的无非是Code1问题正出在你用Class封装了一下String,
楼主可以将String换成int试试看,两个代码的表现仍然同String一样不知道换成int型之后的情况楼主是否理解,其实就是装箱和拆箱,也就是值类型(int)转换成了引用类型了那么你用String的道理应该是相同的,
本来String还属于一种特殊的引用类型,你一用类(class)封装之后,就变成了正宗的引用类型了这样一来,Code1与Code2的表现当然不同了以上纯属个人猜测
Code2里面有个Name对象,但是它是字符串类型,字符串类型在.NET里面具有值类型特点的引用类型,在对它进行浅拷贝的时候可以理解为深拷贝(一个字符一个字符的拷贝,在底层实际上字符串是由字符数组组成的)。如果上面的理解没有问题,那么对于引用类型来说,只是拷贝了一个引用过去,它和原来的对象指向的是同一个地址,字符串拷贝的时候则指向的是不同的地址。如果上面的解释还不清楚的话,可以看看下面用调试工具分析的结果:
首先,我们让程序运行,然后在程序结束之前设置一个断点,以便我们分析当时的内存,为了方便测试,我们可以把代码合并到一起。
接着,断点被命中,此时内存中对于你的Code1来说有两个对象,分别有两个引用指向他们(charon和copy,他们对应的“指针”是:0x0276dd8c和0x0276d874),我们观察这两个地址所对应的内容如下:
0x0276D874 b0 39 19 00 84 d8 76 02 ?9..??v.
0x0276D87C 14 00 00 00 00 00 00 00 ........
和
0x0276DD8C b0 39 19 00 84 d8 76 02 ?9..??v.
0x0276DD94 12 00 00 00 00 00 00 00 ........这里我们可以清晰的看到他们首先都是001939b0这个类型的,也就是Charon类型的,他们的第一个属性是Name,这里对应的是0276d884,记住这是一个地址。第二个属性是age,对于第一个对象来说是0x14=20第二个是0x12=18。好,记住前面的地址了么?0276d884,这两个对象这里的地址是不是一样的?那么就说明这两个对象的Name属性指向的是同一个对象实例(内存),修改其一另一个对象引用对应的地址自然会发生变化(因为他们指向的是同一个地址)再看看Code2,就像前面说的字符串实际上在底层处理的时候是按照字符数组来处理的,那么它的浅拷贝自然也就是逐位复制了,具有值类型的特性。
同样的方法我们可以获得这两个对象的内存布局,如下所示(为了方便我将第二个类改名叫Charon1,所以类型描述符和上面的不一样):0x0276E0F0 a4 3a 19 00 5c c0 76 02 ?:..\?v.
0x0276E0F8 14 00 00 00 00 00 00 00 ........0x0276E2A4 a4 3a 19 00 2c c0 76 02 ?:..,?v.
0x0276E2AC 12 00 00 00 00 00 00 00 ........这里你可以看到第一个属性name对应的分别是0276c05c和0276c02c,这显然是两个不同的地址(引用)区域,那么对他们的修改当然相互不会干扰。最后得出的结论是:人家描述的一点也没错,只是你不了解内部的实现机制而已,以及有一个有点糊涂的String对象导致理解有点困难。以上个人分析,仅供参考。