前言

虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC)。另外,了解内存管理可以帮助我们理解在每一个程序中定义的每一个变量是怎样工作的。

简介

这一节我们将介绍引用类型变量在堆中存储时会产生的问题,同时介绍怎么样使用克隆接口ICloneable去修复这种问题。

复制不仅仅是复制

为了更清晰的阐述这个问题,让我们测试一下在堆中存储值类型变量和引用类型变量时会产生的不同情况。

值类型测试

首先,我们看一下值类型。下面是一个类和一个结构类型(值类型),Dude类包含一个Name元素和两个Shoe元素。我们有一个CopyDude()方法用来复制生成新Dude。

 
  1. public struct Shoe{

  2. public string Color;

  3. }

  4. public class Dude

  5. {

  6. public string Name;

  7. public Shoe RightShoe;

  8. public Shoe LeftShoe;

  9. public Dude CopyDude()

  10. {

  11. Dude newPerson = new Dude();

  12. newPerson.Name = Name;

  13. newPerson.LeftShoe = LeftShoe;

  14. newPerson.RightShoe = RightShoe;

  15. return newPerson;

  16. }

  17. public override string ToString()

  18. {

  19. return (Name + " : Dude!, I have a " + RightShoe.Color +

  20. " shoe on my right foot, and a " +

  21. LeftShoe.Color + " on my left foot.");

  22. }

  23. }

Dude类是一个复杂类型,因为值 类型结构Shoe是它的成员, 它们都将存储在堆中。

当我们执行下面的方法时:

 
  1. public static void Main()

  2. {

  3. Class1 pgm = new Class1();

  4. Dude Bill = new Dude();

  5. Bill.Name = "Bill";

  6. Bill.LeftShoe = new Shoe();

  7. Bill.RightShoe = new Shoe();

  8. Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";

  9. Dude Ted = Bill.CopyDude();

  10. Ted.Name = "Ted";

  11. Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";

  12. Console.WriteLine(Bill.ToString());

  13. Console.WriteLine(Ted.ToString());

  14. }

我们得到了期望的结果:

Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot.
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot.

如果我们把Shoe换成引用类型呢?

引用类型测试

当我们把Shoe改成引用类型时,问题就产生了。

 
  1. public class Shoe{

  2. public string Color;

  3. }

执行同样上面的Main()方法,结果改变了,如下:

Bill : Dude!, I have a Red shoe on my right foot, and a Red on my left foot
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot

这并不是我们期望的结果。很明显,出错了!看下面的图解:

因为现在Shoe是引用类型而不是值类型,当我们进行复制时仅是复制了指针,我们并没有复制指针真正对应的对象。这就需要我们做一些额外的工作使引用类型Shoe像值类型一样工作。

很幸运,我们有一个接口可以帮我们实现:ICloneable。当Dude类实现它时,我们会声明一个Clone()方法用来产生新的Dude复制类。(译外话:复制类及其成员跟原始类不产生任何重叠,即我们所说的深复制)   看下面代码:

 
  1. ICloneable consists of one method: Clone()

  2. public object Clone()

  3. {

  4. }

  5. Here's how we'll implement it in the Shoe class:

  6. public class Shoe : ICloneable

  7. {

  8. public string Color;

  9. #region ICloneable Members

  10. public object Clone()

  11. {

  12. Shoe newShoe = new Shoe();

  13. newShoe.Color = Color.Clone() as string;

  14. return newShoe;

  15. }

  16. #endregion

  17. }

在Clone()方法里,我们创建了一个新的Shoe,克隆所有引用类型变量,复制所有值类型变量,最后返回新的对象Shoe。有些既有类已经实现了ICloneable,我们直接使用即可,如String。因此,我们直接使用Color.Clone()。因为Clone()返回object对象,我们需要进行一下类型转换。

下一步,我们在CopyDude()方法里,用克隆Clone()代替复制:

 
  1. public Dude CopyDude()

  2. {

  3. Dude newPerson = new Dude();

  4. newPerson.Name = Name;

  5. newPerson.LeftShoe = LeftShoe.Clone() as Shoe;

  6. newPerson.RightShoe = RightShoe.Clone() as Shoe;

  7. return newPerson;

  8. }

再次执行主方法Main():

 
  1. public static void Main()

  2. {

  3. Class1 pgm = new Class1();

  4. Dude Bill = new Dude();

  5. Bill.Name = "Bill";

  6. Bill.LeftShoe = new Shoe();

  7. Bill.RightShoe = new Shoe();

  8. Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";

  9. Dude Ted = Bill.CopyDude();

  10. Ted.Name = "Ted";

  11. Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";

  12. Console.WriteLine(Bill.ToString());

  13. Console.WriteLine(Ted.ToString());

  14. }

我们得到了期望的结果:

Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot

下面是图解:

整理我们的代码

在实践中,我们是希望克隆引用类型并复制值类型的。这会让你回避很多不易察觉的错误,就像上面演示的一样。这种错误有时不易被调试出来,会让你很头疼。

因此,为了减轻头疼,让我们更进一步清理上面的代码。我们让Dude类实现IConeable代替使用CopyDude()方法:

 
  1. public class Dude: ICloneable

  2. {

  3. public string Name;

  4. public Shoe RightShoe;

  5. public Shoe LeftShoe;

  6. public override string ToString()

  7. {

  8. return (Name + " : Dude!, I have a " + RightShoe.Color +

  9. " shoe on my right foot, and a " +

  10. LeftShoe.Color + " on my left foot.");

  11. }

  12. #region ICloneable Members

  13. public object Clone()

  14. {

  15. Dude newPerson = new Dude();

  16. newPerson.Name = Name.Clone() as string;

  17. newPerson.LeftShoe = LeftShoe.Clone() as Shoe;

  18. newPerson.RightShoe = RightShoe.Clone() as Shoe;

  19. return newPerson;

  20. }

  21. #endregion

  22. }

在主方法Main()使用Dude.Clone():

 
  1. public static void Main()

  2. {

  3. Class1 pgm = new Class1();

  4. Dude Bill = new Dude();

  5. Bill.Name = "Bill";

  6. Bill.LeftShoe = new Shoe();

  7. Bill.RightShoe = new Shoe();

  8. Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";

  9. Dude Ted = Bill.Clone() as Dude;

  10. Ted.Name = "Ted";

  11. Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";

  12. Console.WriteLine(Bill.ToString());

  13. Console.WriteLine(Ted.ToString());

  14. }

最后得到期望的结果:

Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot.
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot.

特殊引用类型String

在C#中有趣的是,当 System.String 使用操作符“=”时,实际上是进行了克隆(深复制)。你不必担心你只是在操作一个指针,它会在内存中创建一个新的对象。但是,你一定要注意内存的占用问题(译外话:比如为什么在一定情况下我们使用StringBuilder代替String+String+String+String...前者速度稍慢初始化耗多点内存但在大字符串操作上节省内存,后者速度稍快初始化简单但在大字符串操作上耗内存)。如果我们回头去看上面的图解中,你会发现Stirng类型在图中并不是一个针指向另一个内存对象,而是为了尽可能的简单,把它当成值类型来演示了。

总结

在实际工作中,当我们需要复制引用类型变量时,我们最好让它实现ICloneable接口。这样可以让引用类型模仿值类型的使用,从而防止意外的错误产生。你可以看到,慎重得理不同的类型非常重要,因为值类型和引用类型在内存中的分配是不同的。

翻译: http://www.c-sharpcorner.com/UploadFile/rmcochran/chsarp_memory401152006094206AM/chsarp_memory4.aspx

【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复相关推荐

  1. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 栈基本工作原理

    栈基本工作原理 导航 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第 ...

  2. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈

    理解堆与栈 导航 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 ...

  3. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第四节 参数传递对堆栈的影响 1

    前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程序中 ...

  4. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第三节 栈与堆,值类型与引用类型

    前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程序中 ...

  5. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第六节 理解垃圾回收GC,提搞程序性能****

    前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程序中 ...

  6. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第四节 参数传递对堆栈的影响 2

    前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程序中 ...

  7. STM32:堆和栈(Heap Stack)及SRAM存储使用

    STM32堆和栈(Heap & Stack)及SRAM存储使用 编译一个程序,出现下面的信息: 明明程序没有什么内容,为什么变量大小就有RW+ZI=52+1836=1888字节大小了呢,就已经 ...

  8. stm32 堆和栈(stm32 Heap Stack)

    关于堆和栈已经是程序员的一个月经话题,大部分有是基于os层来聊的. 那么,在赤裸裸的单片机下的堆和栈是什么样的分布呢?以下是网摘: 刚接手STM32时,你只编写一个 int main() { whil ...

  9. STM32堆和栈(Heap Stack)及SRAM存储使用

    编译一个程序,出现下面的信息: 明明程序没有什么内容,为什么变量大小就有RW+ZI=52+1836=1888字节大小了呢,就已经使用了1888字节的SRAM空间.让我们打开map文件: 可以看到每个文 ...

最新文章

  1. Context-Based Access Control (CBAC) 基于上下文的访问控制 理论知识
  2. Windows API一日一练(56)SetEndOfFile和GetFileSizeEx函数
  3. gitkraen_超详细!Github团队协作教程(Gitkraken版)
  4. 如果编程替换成中文就会怎样? 程序员看了表示头疼
  5. 曼格短视频小程序V1.8.5版本完整源码
  6. 《C++编程风格(修订版)》——3.2 继承作用域准则
  7. winform中listview选中整行_Excel办公实操,操作区域的3大小技巧,办公中的你使用过吗...
  8. 爬虫可视化点选配置工具之chrome插件简介
  9. 常系数齐次线性微分方程的解法
  10. 【预测模型】基于粒子群算法优化最小二乘支持向量机lssvm实现预测matlab源码
  11. excel冻结窗口怎么设置_excel冻结多行怎么设置-和冻结首行一样哦
  12. android 远程控制windows,Android手机远程控制Windows系统教程
  13. 电脑查看连接过的WiFi密码
  14. 【智能优化算法】基于分段权重和变异反向学习的蝴蝶优化算法求解单目标优化问题附matlab代码
  15. win10忘记密码初始化电脑
  16. 计算机辅助设计基础试题,CAD基础知识自测题
  17. IOS APP 测试方法和测试工具大揭秘
  18. C#栈的实现(数制转换)
  19. 广汉计算机哪个学校学最好,计算机专业四川省的广汉市哪个专业学院比较不错...
  20. ThinkPad按键fn+4电脑黑屏解决办法

热门文章

  1. 正则表达式小应用之对xml格式字符串每个字段加双引号
  2. windows下memcache的安装总结
  3. [转载]Visual Studio 2010敏捷利剑:详解Scrum
  4. sql2005生成sql2000脚本的时候出现“User.UserType: NoLogin 不是SQL Server 2005 的有效选项“ 的解决方案...
  5. Depth-first Search深度优先搜索专题1
  6. 求递归算法时间复杂度:递归树
  7. c语言获取dll文件路径,C语言URLDownloadToFile获取文件下载进度
  8. python request库_【Python爬虫】Request库入门
  9. mysql100个优化技巧_完整篇:100+个MySQL调试和优化技巧(2)
  10. java报错空指针异常_夯实基础:认识一下这10 个深恶痛绝的 Java 异常