[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() ...
随机推荐
- 如何查看memcache的性能
memcache的运行状态可以方便的用 stats 命令显示.首先用telnet 127.0.0.1 11211这样的命令连接上memcache,然后直接输入stats就可以得到当前memcache的 ...
- SQL SERVER 行列转换(动态)
行转列测试数据: --测试数据 if not object_id(N'Tempdb..#T') is null drop table #T Go Create table #T([Name] nvar ...
- Shell脚本编程(二):shell变量
定义变量 定义变量时,变量名不加美元符号($,PHP语言中变量需要),如: your_name="runoob.com" 注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程 ...
- Flink source task 源码分析
http://vinoyang.com/2016/05/05/flink-stream-source/ http://vinoyang.com/2016/12/28/flink-runtime-com ...
- win10安装windows live writer 错误:OnCatalogResult:0x80190194
到官网下载了一个在线安装程序,可是一运行就提示无法安装,显式错误"OnCatalogResult:0x80190194",如下图所示 找到windows live安装程序的安装日志 ...
- html网页采集
UI_Less.pas: unit UI_Less; interface uses Windows, Classes, Messages, Forms, MsHtml, Urlmon, ActiveX ...
- 【iCore4 双核心板_FPGA】例程四:Signal Tapll 实验——逻辑分析仪
实验现象: 三色led轮流闪烁,具体的逻辑分析仪使用教程请参考iCore3逻辑分析仪例程 核心代码: module signal_ctrl( input clk_25m, input rst_n, o ...
- 【6集iCore3_ADP触摸屏驱动讲解视频】6-2 基于FSMC总线的ARM与FPGA通信
视频简介: 该视频介绍基于FSMC总线的ARM与FPGA通信 源视频包下载地址: 链接:http://pan.baidu.com/s/1slJDoQD 密码:tmw7 银杏科技优酷视频发布区: ...
- django template if return false
如果if的参数不存在于context中就会返回false 参考:http://stackoverflow.com/questions/11107028/django-template-if-true- ...
- Qt 密码框不可选中、复制、粘贴、无右键菜单等
在做用户登录.修改密码的时候,往往会用到密码框,其中一些功能要求与普通的输入框不同. 例如:不能选中.复制.粘贴.无右键菜单等功能,当然设置密码不可见是必须的! 一般的密码框:(默认 可以选中,复制, ...