CLR-2-2-引用类型和值类型
引用类型和值类型,是一个老生常谈的问题了。装箱拆箱相信也是猿猿都知,但是还是跟着CLR via C#加深下印象,看有没有什么更加根本和以前被忽略的知识点。
引用类型:
引用类型有哪些这里不过多赘述,来关心一下它在计算机内部的实际操作,引用类型总是从托管堆分配,线程栈上存储的是指向堆上数据的引用地址,首先确立一下四个事实:
内存必须从托管堆分配
堆上分配成员时,CLR要求你必须有一些额外成员(比如同步块索引,类型对象指针)。这些成员必须初始化。
对象中的其他字节总是设为零
从托管堆上分配对象时,可能强制执行一次垃圾回收
所以引用类型对性能是有显著影响的。
值类型:
值类型是CLR提供的轻量级类型,它把实际的字段存储在线程栈上
值类型不受垃圾回收器的限制,所以它的存在缓解了托管堆的压力,也减少了垃圾回收的次数。
值类型都是派生自System.ValueType
所有值类型都是隐式密封的,目的是防止将值类型作为其他引用类型的基类
值类型初始化为空时,默认为0,它不像引用类型是指针,它不会抛出NullReferenceException异常,CLR还为值类型提供了可控类型。
误区防范:根据我自己的经验,要避免对引用类型值类型赋值的错误认识,我们先需要清楚,定义值类型,引用类型的底层实际操作,下面先根据流程图了解一下:
例子:
class SomeRef{public int x;}
struct SomeVal{public int x;} staic void Test
{
SomeRef r1=new SomeRef();
SomeVal v1 =new SomeVal(); r1.x=;
v1.x=; SomeRef r2=r1;
SomeVal v2 =v1;
r1.x=;
v1.x=; string a="QWER";
string b=a;
a="TYUI";
}
这样类似的例子,相信只要讲到引用类型,值类型,就一定会见到,继续复习一下。
首先揭晓几轮复制后的结构:r1.x=8,r2.x=8 v1.x=9 v2.x=5 a="TYUI" b="QWER"
简单分析一下:
r1 ,r2在线程栈上存储的是同一个指向内存堆的地址,当r1值改变时,其实是直接改变内存堆里的内容,自然r1,r2全部变成了8。
而v1,v2是独立存储在线程栈上的,v1值改变时,只是单单改变v1线程栈里的值,自然v2=5,v1=9。
而a,b的值为什么不像上面r1.x一样变化呢,它们不是引用类型吗,这就需要去看看上面的流程图,因为你在给a改变赋值时,其实是在托管堆上开辟了一个新的空间,你传给a的是一个新的地址,而b还指向原来的老地址。
结合上面的三个图和示例,对于引用类型和值类型构建相信应该有一个清楚的理解了。
使用值类型的一些建议:
值类型相对于引用类型,性能上更有优势,但是考虑在业务上的问题,值类型一般需要满足下面的全部条件,才是适合定义为值类型:
类型具有基元类型的行为。也就是说,是十分简单的类型,没有成员会修改类型的任何实例。如果类型没有提供会更改其他字段的成员,就称为不可变类型(immutable)。事实上,对于许多值类型,我们都建议将全部字段标记为readonly。
类型不需要从其他类型继承
类型不派生出其他类(隐式密封)。
类型大小也应考虑:
因为实参默认以传值方式传递,造成对值类型实例中的字段进行复制,如果值类型过于大会对性能造成损害。
同样,当顶一个值类型的方法返回时,实例中的字段会复制到调用者分配的内存,也可能造成性能的损害。
所以,必须满足以下任意条件:
类型实例较小(16字节或更小)
类型实例较大(大于16字节),但不作为方法实参传递,也不从方法传递
值类型的局限:
值类型有两种形式:未装箱和已装箱,而引用类型一直是已装箱。
值类型从System.ValueType派生,System.ValueType重写了Equals和GetHashCode方法。生成哈希码时,会将对象的实例字段的值考虑在内。所以定义自己的值类型时,因重写Equals和GetHashCode方法。
值类型不能被继承,它自己的方法不能是抽象的,所有都是隐式密封的。
值类型不在内存堆中分配,所以一个实例的方法不再活动时,分配给值类型的内存空间会被释放,而没有垃圾回收机制来处理它。
值类型的装箱拆箱:
例如,ArrayList不断的添加值类型进入数组时,就会发生不断的装箱操作,因为它的Add方法参数是object类型,自然装箱就不可避免,自然也会造成性能的损失(FCL现在提供了泛型集合类,System.Collection.Generic.List<T>,它不需要装箱拆箱操作。使得性能提升不少)。
装箱相关的含义相信不用过多解释,我们来关心一下,内存中的变化,看看它是如何对性能造成影响的。
装箱:
在托管堆中分配内存。内存大小时值类型各字段所需的内存加上两个额外成员(托管堆所有对象都有)类型对象指针和同步块索引所需的内存量。
值类型的字段值复制到堆内存的空间中。
返回堆上对应的地址
然后,一个值类型就变成了引用类型。
拆箱:
根据引用类型的地址找到堆内存上的值
将值复制给值类型
拆箱的代价比装箱小得多
装箱拆箱注意点:
下面通过几个示例,来熟悉一下装箱拆箱的过程,并学会如何避免错误的判定装箱拆箱,CLR via C#这两个实例对装箱拆箱的理解非常有帮助:
internal struct Point : IComparable
{
private Int32 m_x,m_y;
public Point(int x,int y)
{
m_x = x;
m_y = y;
} public override string ToString()
{
return String.Format("({0},{1})", m_x.ToString(), m_y.ToString());
} public int CompareTo(Point p)
{
return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y) - Math.Sqrt(p.m_x * p.m_x + p.m_y * p.m_y));
} public int CompareTo(object obj)
{
if (GetType() != obj.GetType())
{
throw new ArgumentException("o is not a point");
}
return CompareTo((Point)obj);
}
}
static void Main(string[] args)
{
//在栈上创建两个实例
Point p1 = new Point(,);
Point p2 = new Point(,); //调用Tostring不装箱
Console.WriteLine(p1.ToString()); //调用非虚方法GetType装箱
Console.WriteLine(p1.GetType()); //调用CompareTo,不装箱
Console.WriteLine(p1.CompareTo(p2)); //p1装箱
IComparable C = p1;
Console.WriteLine(C.GetType()); //不装箱,调用的CompareTo(object)
Console.WriteLine(p1.CompareTo(C)); //不装箱,调用的CompareTo(object)
Console.WriteLine(p1.CompareTo(p2)); Console.ReadKey();
}
1.调用ToString
不装箱,因为ToString是从ValueType继承的虚方法,中间没有类型转换的发生,不需要进行装箱,另外注意的是:Equals,GetHashCode,ToString都是从ValueTye继承的虚方法,由于值类型都是密封类,无法派生,所以只要你的值类型重写了这些方法,并没有去调用基类的实现,那么是不会发生装箱的,如果你去调用基类的实现,或者你没有实现这些方法,那么还是可能发生装箱。
2.调用GetType
GetType是继承自Object,并且不能被重写,所以无论如何值类型对其调用都会发生装箱,另外MemberwiseClone方法也是如此。
3.第一次调用CompareTo方法
因为Point里面有了类型为Point的参数CompareTo方法,不会发生装箱操作
4.p1转换为ICompable
确认过眼神,这一定是一个装箱。
5.第二次调用CompareTo方法
虽然这次调用的是参数为object的方法,但是注意的是:首先我们Point实现了这个重载,另外传进去的是个ICompable,自然不会发生装箱(另外,如果Point本身没有这个方法呢?当然会装箱,因为它不得不去调用父类的方法,而父类是一个引用类型,自然需要进行一次装箱操作)
6.第三次调用CompareTo方法
c是ICompable,而ICompable在托管堆上也有对应的方法,也不会有装箱发生。
internal struct point
{
private int m_x,m_y; pulic point(int x,int y)
{
m_x=x;
m_y=y;
} public void change(int x,int y)
{
m_x=x;
m_y=y;
} public ovveride String ToString()
{
return String.Format("{0},{1}",m_x.ToString.m_y.ToString());
} }
public static void Main()
{
Point p = new Point(,);
Console.WriteLine(p); p.Change(,);
Console.WriteLine(p); Object o=p;
Console.WriteLine(o); ((Point) o).Change(,);
Console.WriteLine(o);
}
结果:当然是 (1,1)(2,2) (2,2) (2,2) 前面三次的结果很好理解,第四次为什么是(2,2),因为object没有change方法,它等拆箱拆到线程栈新的地址上,于是后面的操作则是在线程栈上进行,对o堆上的内容没有任何影响
internale interface IChangeBoxedPoint
{
void Change(int x,int y);
}
internal struct point
{
private int m_x,m_y; pulic point(int x,int y)
{
m_x=x;
m_y=y;
} public void change(int x,int y)
{
m_x=x;
m_y=y;
} public ovveride String ToString()
{
return String.Format("{0},{1}",m_x.ToString.m_y.ToString());
} }
public static void Main()
{
Point p =new p(,);
Console.WriteLine(p); p.Change(,);
Console.WriteLine(p); Objec o =p;
Console.WriteLine(o); ((Point) o).Change(,);
Console.WriteLine(o); ((IChangeBoxedPoint) p).Change(,);
Console.WriteLine(p); ((IChangeBoxedPoint) o).Change(,);
Console.WriteLine(o);
}
结果:前面四次的结果应该是显而易见了,(1,1)(2,2) (2,2) (2,2),那么第五次呢,来简单分析一下p装箱为IChangeBoxedPoint,然后把堆上对应的p的m_x,m_y改为4,4,但是对p输出时堆上的内容不仅回收了,而且输出的是原来p线程栈上的内筒,仍然还是刚刚的(2,2),第六步,o没有任何装箱拆箱操作,当然是预期的(5,5)
CLR-2-2-引用类型和值类型的更多相关文章
- [CLR via C#]引用类型和值类型
一.引用类型与值类型的区别 CLR支持两种类型:引用类型和值类型.引用类型总是从托管堆上分配的,C#的new操作符会返回对象的内存地址.使用引用类型时,必须注意到一些性能问题. 1)内存必须从托管堆上 ...
- 《CLR via C#》读书笔记--基元类型、引用类型和值类型
编程语言的基元类型 编译器直接支持的数据类型称为基元类型.基元类型直接映射到Framework类库中存在的类型.例如:C#中的int直接映射到System.Int32类型.下表给出了C#基元类型与对应 ...
- 《CLR via C#》读书笔记(5)基元类型、引用类型和值类型
5.1 基元类型 编译器直接支持的数据类型称为基元类型(primitive type). 以下4行到吗生成完全相同的IL int a = 0; //最方便的语法 System.Int32 b = 0; ...
- 【CLR Via C#】第5章 基元类型、引用类型、值类型
第二遍看这本书,决定记录一下加深印象. 1,基元类型 什么事基元类型?基元类型是直接映射到FrameWork类库(FCL)中存在的类型,编译器直接支持的数据类型.比如int直接映射到System.In ...
- CLR via C#深解笔记三 - 基元类型、引用类型和值类型 | 类型和成员基础 | 常量和字段
编程语言的基元类型 某些数据类型如此常用,以至于许多编译器允许代码以简化的语法来操纵它们. System.Int32 a = new System.Int32(); // a = 0 a = 1 ...
- [CLR via C#]5.2 引用类型和值类型
原文:[CLR via C#]5.2 引用类型和值类型 CLR支持两种类型:引用类型和值类型. 虽然FCL中大多数都是引用类型,但开发人员用的最多的还是值类型.引用类型总是在托管堆上分配的,C#的ne ...
- CLR via C#读书日记一' 引用类型和值类型'
CLR支持两种类型:引用类型和值类型. 引用类型总是在托管堆上分配的,C#的new操作符会返回对象的内存地址——也就是指向对象数据的内存地址. 使用引用类型必须注意到一些问题: 1)内存必须从托管堆上 ...
- <NET CLR via c# 第4版>笔记 第5章 基元类型、引用类型和值类型
5.1 编程语言的基元类型 c#不管在什么操作系统上运行,int始终映射到System.Int32; long始终映射到System.Int64 可以通过checked/unchecked操作符/语句 ...
- 重温CLR(四)基元类型、引用类型、值类型
编程语言的基元类型 编译器直接支持的数据类型称为基元类型(primitive type).基元类型直接映射到framework类型(fcl)中存在的类型. 下表列出fcl类型 从另一个角度,可以认为C ...
- CLR via C#(02)-基元类型、引用类型、值类型
http://www.cnblogs.com/qq0827/p/3281150.html 一. 基元类型 编译器能够直接支持的数据类型叫做基元类型.例如int, string等.基元类型和.NET框架 ...
随机推荐
- 【Alpha】阶段总结报告
团队成员 陈家权 031502107 赖晓连 031502118 雷晶 031502119 林巧娜 031502125 庄加鑫 031502147 一.项目预期计划及现实进展 项目预期计划 现实进展 ...
- 《团队-OldNote-项目总结》
我们小组做的是手机便签的app---OldNote 最开始我们想解决普通手机便签无法进行语音和照片的备忘这一问题,但是由于没有做过拍照和录音的经验怕由于技术原因无法达成目的,就没敢写在需求分析中.当完 ...
- C语言第二周作业——分支结构
一.PTA实验作业 题目1.7-1计算分段函数 本题目要求计算下列分段函数f(x)的值: 1实验代码 double x,result; scanf("%lf",&x); i ...
- Alpha冲刺Day9
Alpha冲刺Day9 一:站立式会议 今日安排: 经过为期5天的冲刺,基本完成企业人员模块的开发.因第三方机构与企业存在委托的关系.第三方人员对于风险的自查.风险列表的展示以及自查风险的统计展示(包 ...
- splinter web测试框架
1.安装谷歌浏览器驱动(windows把驱动解压放在Python.exe同级目录即可) http://chromedriver.storage.googleapis.com/index.html 注意 ...
- 在Windows上安装「算法 第四版」组件
这篇文档将向你介绍如何在Windows系统上安装本书将用到的Java开发环境,同时我们也提供了一个手把手的.使用我们提供的DrJava工具或者用命令行来创建.编译和运行你的第一个Java程序的手册,这 ...
- SDOI2017 相关分析
把两个式子拆开 Σ(xi-px)(yi-py) =Σ xiyi + py * Σ xi - px * Σ yi + Σ 1* px * py Σ (xi-px)² = Σ xi² + px * Σ ...
- nyoj 移位密码
移位密码 时间限制:1000 ms | 内存限制:65535 KB 难度:0 描述 移位密码是最简单的一类代替密码,具体算法就是将字母表的字母右移k个位置(k<26),并对字母表长度作模 ...
- mingw打dll ,lib包命令和调用
1,下面的命令行将这个代码编译成 dll. gcc mydll.c -shared -o mydll.dll -Wl,--out-implib,mydll.lib 其中 -shared 告诉gcc d ...
- python3 常用模块
一.time与datetime模块 在Python中,通常有这几种方式来表示时间: 时间戳(timestamp):通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量.我们 ...