[No0000146]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈3/4
前言
简介
复制不仅仅是复制
值类型测试
- public struct Shoe{
- public string Color;
- }
- public class Dude
- {
- public string Name;
- public Shoe RightShoe;
- public Shoe LeftShoe;
- public Dude CopyDude()
- {
- Dude newPerson = new Dude();
- newPerson.Name = Name;
- newPerson.LeftShoe = LeftShoe;
- newPerson.RightShoe = RightShoe;
- return newPerson;
- }
- public override string ToString()
- {
- return (Name + " : Dude!, I have a " + RightShoe.Color +
- " shoe on my right foot, and a " +
- LeftShoe.Color + " on my left foot.");
- }
- }
Dude类是一个复杂类型,因为值 类型结构Shoe是它的成员, 它们都将存储在堆中。
- public static void Main()
- {
- Class1 pgm = new Class1();
- Dude Bill = new Dude();
- Bill.Name = "Bill";
- Bill.LeftShoe = new Shoe();
- Bill.RightShoe = new Shoe();
- Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";
- Dude Ted = Bill.CopyDude();
- Ted.Name = "Ted";
- Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";
- Console.WriteLine(Bill.ToString());
- Console.WriteLine(Ted.ToString());
- }
我们得到了期望的结果:
- 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换成引用类型呢?
引用类型测试
- public class Shoe{
- public string Color;
- }
执行同样上面的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
这并不是我们期望的结果。很明显,出错了!看下面的图解:
- ICloneable consists of one method: Clone()
- public object Clone()
- {
- }
- Here's how we'll implement it in the Shoe class:
- public class Shoe : ICloneable
- {
- public string Color;
- #region ICloneable Members
- public object Clone()
- {
- Shoe newShoe = new Shoe();
- newShoe.Color = Color.Clone() as string;
- return newShoe;
- }
- #endregion
- }
在Clone()方法里,我们创建了一个新的Shoe,克隆所有引用类型变量,复制所有值类型变量,最后返回新的对象Shoe。有些既有类已经实现了ICloneable,我们直接使用即可,如String。因此,我们直接使用Color.Clone()。因为Clone()返回object对象,我们需要进行一下类型转换。
- public Dude CopyDude()
- {
- Dude newPerson = new Dude();
- newPerson.Name = Name;
- newPerson.LeftShoe = LeftShoe.Clone() as Shoe;
- newPerson.RightShoe = RightShoe.Clone() as Shoe;
- return newPerson;
- }
再次执行主方法Main():
- public static void Main()
- {
- Class1 pgm = new Class1();
- Dude Bill = new Dude();
- Bill.Name = "Bill";
- Bill.LeftShoe = new Shoe();
- Bill.RightShoe = new Shoe();
- Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";
- Dude Ted = Bill.CopyDude();
- Ted.Name = "Ted";
- Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";
- Console.WriteLine(Bill.ToString());
- Console.WriteLine(Ted.ToString());
- }
我们得到了期望的结果:
- 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
下面是图解:
整理我们的代码
- public class Dude: ICloneable
- {
- public string Name;
- public Shoe RightShoe;
- public Shoe LeftShoe;
- public override string ToString()
- {
- return (Name + " : Dude!, I have a " + RightShoe.Color +
- " shoe on my right foot, and a " +
- LeftShoe.Color + " on my left foot.");
- }
- #region ICloneable Members
- public object Clone()
- {
- Dude newPerson = new Dude();
- newPerson.Name = Name.Clone() as string;
- newPerson.LeftShoe = LeftShoe.Clone() as Shoe;
- newPerson.RightShoe = RightShoe.Clone() as Shoe;
- return newPerson;
- }
- #endregion
- }
在主方法Main()使用Dude.Clone():
- public static void Main()
- {
- Class1 pgm = new Class1();
- Dude Bill = new Dude();
- Bill.Name = "Bill";
- Bill.LeftShoe = new Shoe();
- Bill.RightShoe = new Shoe();
- Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";
- Dude Ted = Bill.Clone() as Dude;
- Ted.Name = "Ted";
- Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";
- Console.WriteLine(Bill.ToString());
- Console.WriteLine(Ted.ToString());
- }
最后得到期望的结果:
- 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类型在图中并不是一个针指向另一个内存对象,而是为了尽可能的简单,把它当成值类型来演示了。
总结
Even though with the .NET framework we don't have to actively worry about memory management and garbage collection (GC), we still have to keep memory management and GC in mind in order to optimize the performance of our applications. Also, having a basic understanding of how memory management works will help explain the behavior of the variables we work with in every program we write. In this article we'll cover an issue that arises from having reference variables in the heap and how to fix it using ICloneable.
A Copy Is Not A Copy.
To clearly define the problem, let's examine what happens when there is a value type on the heap versus having a reference type on the heap. First we'll look at the value type. Take the following class and struct. We have a Dude class which contains a Name element and two Shoe(s). We have a CopyDude() method to make it easier to make new Dudes.
public struct Shoe{
public string Color;
}
public class Dude
{
public string Name;
public Shoe RightShoe;
public Shoe LeftShoe;
public Dude CopyDude()
{
Dude newPerson = new Dude();
newPerson.Name = Name;
newPerson.LeftShoe = LeftShoe;
newPerson.RightShoe = RightShoe;
return newPerson;
}
public override string ToString()
{
return (Name + " : Dude!, I have a " + RightShoe.Color +
" shoe on my right foot, and a " +
LeftShoe.Color + " on my left foot.");
}
}
Our Dude class is a variable type and because the Shoe struct is a member element of the class they both end up on the heap.
When we run the following method:
public static void Main()
{
Class1 pgm = new Class1();
Dude Bill = new Dude();
Bill.Name = "Bill";
Bill.LeftShoe = new Shoe();
Bill.RightShoe = new Shoe();
Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";
Dude Ted = Bill.CopyDude();
Ted.Name = "Ted";
Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";
Console.WriteLine(Bill.ToString());
Console.WriteLine(Ted.ToString());
}
We get the expected output:
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.
What happens if we make the Shoe a reference type? Herein lies the problem. If we change the Shoe to a reference type as follows:
public class Shoe{
public string Color;
}
and run the exact same code in Main(), look how our input changes:
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
The Red shoe is on the other foot. This is clearly an error. Do you see why it's happening? Here's what we end up with in the heap.
Because we now are using Shoe as a reference type instead of a value type and when the contents of a reference type are copied only the pointer is copied (not the actual object being pointed to), we have to do some extra work to make our Shoe reference type behave more like a value type.
Luckily, we have an interface that will help us out: ICloneable. This interface is basically a contract that all Dudes will agree to and defines how a reference type is duplicated in order to avoid our "shoe sharing" error. All of our classes that need to be "cloned" should use the ICloneable interface, including the Shoe class.
ICloneable consists of one method: Clone()
public object Clone()
{
}
Here's how we'll implement it in the Shoe class:
public class Shoe : ICloneable
{
public string Color;
#region ICloneable Members
public object Clone()
{
Shoe newShoe = new Shoe();
newShoe.Color = Color.Clone() as string;
return newShoe;
}
#endregion
}
Inside the Cone() method, we just make a new Shoe, clone all the reference types and copy all the value types and return the new object. You probably noticed that the string class already implements ICloneable so we can call Color.Clone(). Because Clone() returns a reference to an object, we have to "retype" the reference before we can set the Color of the shoe.
Next, in our CopyDude() method we need to clone the shoes instead of copying them
public Dude CopyDude()
{
Dude newPerson = new Dude();
newPerson.Name = Name;
newPerson.LeftShoe = LeftShoe.Clone() as Shoe;
newPerson.RightShoe = RightShoe.Clone() as Shoe;
return newPerson;
}
Now, when we run main:
public static void Main()
{
Class1 pgm = new Class1();
Dude Bill = new Dude();
Bill.Name = "Bill";
Bill.LeftShoe = new Shoe();
Bill.RightShoe = new Shoe();
Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";
Dude Ted = Bill.CopyDude();
Ted.Name = "Ted";
Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";
Console.WriteLine(Bill.ToString());
Console.WriteLine(Ted.ToString());
}
We get:
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
Which is what we want.
Wrapping Things Up.
So as a general practice, we want to always clone reference types and copy value types. (It will reduce the amount of aspirin you will have to purchase to manage the headaches you get debugging these kinds of errors.)
So in the spirit of headache reduction, let's take it one step further and clean up the Dude class to implement ICloneable instead of using the CopyDude() method.
public class Dude: ICloneable
{
public string Name;
public Shoe RightShoe;
public Shoe LeftShoe;
public override string ToString()
{
return (Name + " : Dude!, I have a " + RightShoe.Color +
" shoe on my right foot, and a " +
LeftShoe.Color + " on my left foot.");
}
#region ICloneable Members
public object Clone()
{
Dude newPerson = new Dude();
newPerson.Name = Name.Clone() as string;
newPerson.LeftShoe = LeftShoe.Clone() as Shoe;
newPerson.RightShoe = RightShoe.Clone() as Shoe;
return newPerson;
}
#endregion
}
And we'll change the Main() method to use Dude.Clone()
public static void Main()
{
Class1 pgm = new Class1();
Dude Bill = new Dude();
Bill.Name = "Bill";
Bill.LeftShoe = new Shoe();
Bill.RightShoe = new Shoe();
Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";
Dude Ted = Bill.Clone() as Dude;
Ted.Name = "Ted";
Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";
Console.WriteLine(Bill.ToString());
Console.WriteLine(Ted.ToString());
}
And our final output is:
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.
So all is well.
Something interesting to note is that the assignment operator (the "=" sign) for the System.String class actually clones the string so you don't have to worry about duplicate references. However you do have to watch our for memory bloating. If you look back at the diagrams, because the string is a reference type it really should be a pointer to another object in the heap, but for simplicity's sake, it's shown as a value type.
In Conclusion.
As a general practice, if we plan on ever copying of our objects, we should implement (and use) ICloneable. This enables our reference types to somewhat mimic the behavior of a value type. As you can see, it is very important to keep track of what type of variable we are dealing with because of differences in how the memory is allocated for value types and reference types.
In the next article, we'll look at a way to reduce our code "footprint" in memory.
Until then,
Happy coding.
[No0000146]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈3/4的更多相关文章
- [No0000145]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈2/4
前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程 ...
- [No0000144]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈1/4
前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程 ...
- [No0000147]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈4/4
前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程 ...
- Java 堆内存与栈内存异同(Java Heap Memory vs Stack Memory Difference)
--reference Java Heap Memory vs Stack Memory Difference 在数据结构中,堆和栈可以说是两种最基础的数据结构,而Java中的栈内存空间和堆内存空间有 ...
- java - Stack栈和Heap堆的区别
首先分清楚Stack,Heap的中文翻译:Stack—栈,Heap—堆. 在中文里,Stack可以翻译为“堆栈”,所以我直接查找了计算机术语里面堆和栈开头的词语: 堆存储 ...
- 内存,堆,栈,heap,stack,data
1. 基本类型占一块内存. 引用类型占两块. 2. 类是静态概念. 函数中定义的基本类型变量和对象的引用类型变量都在函数的栈内存. 局部变量存在栈内存. new创建的对象和数组,存在堆内存. java ...
- iOS:堆(heap)和栈(stack)的理解
Objective-C的对象在内存中是以堆的方式分配空间的,并且堆内存是由你释放的,即release 栈由编译器管理自动释放的,在方法中(函数体)定义的变量通常是在栈内,因此如果你的变量要跨函数的话就 ...
- iOS中的堆(heap)和栈(stack)的理解
操作系统iOS 中应用程序使用的计算机内存不是统一分配空间,运行代码使用的空间在三个不同的内存区域,分成三个段:“text segment “,“stack segment ”,“heap segme ...
- stm32 堆和栈(stm32 Heap & Stack)【worldsing笔记】
关于堆和栈已经是程序员的一个月经话题,大部分有是基于os层来聊的. 那么,在赤裸裸的单片机下的堆和栈是什么样的分布呢?以下是网摘: 刚接手STM32时,你只编写一个 int main() ...
随机推荐
- 设置log rotation避免tomcat catalina.out文件增长过大
创建logrotate配置文件 $ vi /etc/logrotate.d/tomcat 添加以下内容: /opt/tomcat/logs/catalina.out { copytruncate da ...
- 适合初学者的python实际例子
最近在github上发现了一个有意思的项目,很适合初学者学习python代码. 学习一门语言刚开始的时候是很枯燥的,各种概念语法以及无聊的打印都会让人失去更进一步学习的动力. 很多同学在学习了一段时间 ...
- R语言编程艺术#02#矩阵(matrix)和数组(array)
矩阵(matrix)是一种特殊的向量,包含两个附加的属性:行数和列数.所以矩阵也是和向量一样,有模式(数据类型)的概念.(但反过来,向量却不能看作是只有一列或一行的矩阵. 数组(array)是R里更一 ...
- 空间谱专题13:联合解算DOA(ML/AP)
其中作者:桂. 时间:2017-10-16 07:51:40 链接:http://www.cnblogs.com/xingshansi/p/7675380.html 前言 主要记录二维测向中,分别利 ...
- Roller5.0.3安装配置部署 step by step
一.下载roller 下载地址:http://roller.apache.org/downloads/downloads.html下载下来之后,解压包含两部份doc.webapps 二.准备环境 1. ...
- linux每日命令(1):ls命令
ls命令是linux下最常用的命令.ls命令就是list的缩写缺省下ls用来打印出当前目录的清单如果ls指定其他目录那么就会显示指定目录里的文件及文件夹清单. 通过ls 命令不仅可以查看linux ...
- CTimeSpan
要获取两个时间差,如两个CTime的时间差,可以使用MFC中的CTimeSpan类. CTime time1 = CTime::GetCurrentTime(); CTime time2 = CTim ...
- 【转】WPF自定义控件与样式(8)-ComboBox与自定义多选控件MultComboBox
一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等. 本文主要内容: 下拉选择控件ComboBox的自定义样式及扩展: 自定义多选控件Mul ...
- Windows平台使用RMAN命令自动删除Oracle过期归档日志的方法
自动删除Oracle过期归档日志的思路如下: 1.编写自动执行的bat脚本文件: 2.Windows设置定期执行计划 OracleArchLogClear.bat D:/Oracle/product/ ...
- input 手机号
关于input手机号的验证 一.手机号的判断方法: function isPhoneTel(n){ var reg = /^1[3|4|5|8]\d{9}$/; if(!!(reg.test(n))) ...