c#中的引用类型和值类型
一,c#中的值类型和引用类型
众所周知在c#中有两种基本类型,它们分别是值类型和引用类型;而每种类型都可以细分为如下类型:

- 什么是值类型和引用类型
- 什么是值类型:
- 进一步研究文档,你会发现所有的结构都是抽象类型System.ValueType的直接派生类,而System.ValueType本身又是直接从System.Object派生的。根据定义所知,所有的值类型都必须从System.ValueType派生,所有的枚举都从System.Enum抽象类派生,而后者又从System.ValueType派生。
- 所有的值类型都是隐式密封的(sealed),目的是防止其他任何类型从值类型进行派生。
- 什么是引用类型:
- 在c#中所有的类都是引用类型,包括接口。
- 什么是值类型:
- 区别和性能
- 区别:
- 值类型通常被人们称为轻量级的类型,因为在大多数情况下,值类型的的实例都分配在线程栈中,因此它不受垃圾回收的控制,缓解了托管堆中的压力,减少了应用程序的垃圾回收的次数,提高性能。
- 所有的引用类型的实例都分配在托管堆上,c#中new操作符会返回一个内存地址指向当前的对象。所以当你在创建个一个引用类型实例的时候,你必须要考虑以下问题:
- 内存是在托管堆上分配的
- 在分配每一个对象时都会包含一些额外的成员(类型对象指针,同步块索引),这些成员必须初始化
- 对象中的其他字节总是设为零
- 在分配对象时,可能会进行一次垃圾回收操作(如果托管堆上的内存不够分配一次对象时)
- 性能:
- 在设计一个应用程序时,如果都是应用类型,那么应用程序的性能将显著下降,因为这会加大托管堆的压力,增加垃圾回收的次数。
- 虽然值类型是一个轻量级的类型,但是如果大量的使用值类型的话,也会有损应用程序的性能(例如下面要讲的装箱和拆箱操作,传递实例较大的值类型,或者返回较大的值类型实例)。
- 由于值类型实例的值是自己本身,而引用类型的实例的值是一个引用,所以如果将一个值类型的变量赋值给另一个值类型的变量,会执行一次逐字段的复制,将引用类型的变量赋值给另一个引用类型的变量时,只需要复制内存地址,所以在对大对象进行赋值时要避免使用值类型。例如下面的代码

1 class SomRef
2 {
3 public int x;
4 }
5 struct SomeVal {
6 public int x;
7 }
8 class Program {
9 static void ValueTypeDemo() {
10 SomRef r1 = new SomRef();//在堆上分配
11 SomeVal v1 = new SomeVal();//在栈上分配
12 r1.x = 5;//提领指针
13 v1.x = 5;//在栈上修改
14 SomRef r2 = r1;//只复制引用(指针)
15 SomeVal v2 = v1;//在栈上分配并复制成员
16 }
17 }

- 区别:
- 常见误区
- 引用类型分配在托管堆上,值类型分配在线程栈上:其实这种说法的前半部分是对的,后半部分是错的。因为变量的值在它声明的位置存储的,所以假如某一个引用类型中有一个值类型的变量, 那么该变量的值总是和该引用类型的对象的其它数据在一起,也就是分配在堆上。(只有局部变量(方法内部声明的变量)和方法的参数在栈上)
- 结构是轻量级的类:这种错误的信息主要是因为有人认为值类型不应该有方法或者其它有意义的行为-它们应该作为简单的数据转移来使用,所以很多人分不清DateTime到底是值类型还是引用类型。
- 对象在c#中默认的是用过引用传递的:其实在调用方法的时候,参数值(对象的一个引用)是以传值得方式传递的,如果你想以引用方式传递的话,可以使用ref或者out关键字。
二,值类型的装箱和拆箱操作
1 int i = 5;
2 object o = i;
3 int j = (int)o;
4 Int16 y=(Int16)o;- 什么是装箱,什么是拆箱
- 什么是装箱:所谓装箱就是将值类型转化为引用类型的过程(例如上面代码的第2行),在装箱时,你需要知道编译器内部都干了什么事:
- 在托管堆中分配好内存,分配的内存量是值类型的各个字段需要的内存量加上托管堆上所以对象的两个额外成员(类型对象指针,同步块索引)需要的内存量
- 值类型的字段复制到新分配的堆内存中
- 返回对象的地址,这个地址就是这个对象的引用
- 什么是装箱:将已装箱的值类型实例(此时它已经是引用类型了)转化成值类型的过程(例如上面代码的第3行),注意:拆箱不是直接将装箱过程倒过来,拆箱的代价比装箱要低的多,拆箱其实就是获取一个指针的过程。一个已装箱的实例在拆箱时,编译器在内部都干了下面这些事:
- 如果包含了“对已装箱类型的实例引用”的变量为null时,会抛出一个NullReferenceException异常。
- 如果引用指向的对象不是所期待的值类型的一个已装箱实例,会抛出一个InvalidCastException异常(例如上面代码的第4行)。
- 什么是装箱:所谓装箱就是将值类型转化为引用类型的过程(例如上面代码的第2行),在装箱时,你需要知道编译器内部都干了什么事:
- 它们在什么情况下发生,以及如何避免

1 static void Main(string[] args)
2 {
3 int v = 5;
4 object o = v;
5 v = 123;
6 Console.WriteLine(v+","+(int)o);
7 }
通过上面的分析我们已经知道了,装箱和拆箱/复制操作会对应用程序的速度和内存消耗产生不利的影响(例如消耗内存,增加垃圾回收次数,复制操作),所以我们应该注意编译器在什么时候会生成代码来自动这些操作,并尝试手写这些代码,尽量避免自动生成代码的情况。
你能一眼从上面的代码中看出进行了几次装箱操作吗?正取答案是3次。分别进行了哪三次呢,我们来看一下:第一次object o=v;第二次在执行 Console.WriteLine(v+","+(int)o);时将v进行装箱,然后对o进行拆箱后又装箱。也就是说装箱过程总是在我们不经意的时候进行的,所以只有我们充分了解了装箱的内部机制,才能有效的避免装箱操作,从而提高应用程序的性能。所以对上面的代码进行如下修改可以减少装箱次数,从而提高性能:

1 static void Main(string[] args)
2 {
3 int v = 5;
4 object o = v;
5 v = 123;
6 Console.WriteLine(v.ToString() + "," + ((int)o).ToString());//((int)o).ToString()代码本身没有任何意义,只为演示装箱和拆箱操作
7 }
- 下面来讨论一下编译器都会在什么时候自动生成代码来完成这些操作
- 使用非泛型集合时:比如ArrayList,因为这些集合需要的对象都是object,如果你将一个值类型的对象添加到集合中时会执行一次装箱操作,当你取值时会执行一次拆箱操作,所以在应用程序中应避免使用这种非泛型的集合。
- 大家都知道System.Object是所有类型的基类,当你调用object类型的非虚方法时会进行装箱操作(例如GetType方法)。在调用object的虚方法时,如果你的值类型没有重写虚方法也要进行装箱操作,所以在定义自己的值类型时,应重写object内部的虚方法(例如ToString方式)
- 将值类型转化为接口类型时也会进行装箱操作,这是因为接口类型必须包含对堆上的一个对象的引用。
三,泛型的出现(本节只简单介绍泛型对装箱和拆箱所起的作用,关于泛型的具体细节请参考下一篇文章)
- 什么泛型
- 泛型是CLR和编程语言提供的一种特殊机制,它在c#2中才被提供出来。
- 它对避免装箱有什么作用?
- 在使用泛型时需要指定要装配的类型,这样可以减少装箱操作,比如下面的代码

1 static void Main(string[] args)
2 {
3 ArrayList dateList = new ArrayList {
4 DateTime.Now
5 };
6
7 IList<DateTime> dateT = new List<DateTime> {
8 DateTime.Now
9 };
10 }
使用ArrayList时,每添加一个时间都会进行一次装箱操作,而使用List<DateTime>时就不会进行装箱操作,从而提高应用程序的性能。
- 在使用泛型时需要指定要装配的类型,这样可以减少装箱操作,比如下面的代码
- C#中常见的泛型集合:
Queue<T>;
Stack<T>;
List<T>;
Dictionary<Tkey,Tvalue>;
HashSet<T>;
在使用这些集合之前我们必须要理解每一种集合的工作原理(没事自己可以实现一下),了解每一种集合的适合场合,这样才能写出高效的代码。
- 什么泛型
四,在设计时如何选择类和结构体
在面试的时候,我们经常被问的一个问题(还有另外一个问题,如何选择抽象类和接口,下次我会单独聊聊这个问题),下面我们来聊聊在设计时应该如何选择结构体和类
- 什么是结构体
- 结构体是一种特殊的值类型,所以它拥有值类型所以的特权(实例一般分配在线程栈上)和限制(不能被派生,所以没有 abstract 和 sealed,未装箱的实例不能进行线程同步的访问)。
- 什么情况下选择结构体,什么情况下选择类
- 在大多数的情况下,都应该选择类,除非满足以下情况,才考虑选择结构体:
- 类型具有基元类型的行为
- 类型不需要从其它任何类型继承
- 类型也不会派生出任何其它类型
- 类型的实例较小(约为16字节或者更小)
- 类型的实例较大,但是不作为方法的参数传递,也不作为方法的返回值。
- 什么是结构体
都说程序是一门注重实践的学科,但是也只有熟悉理解了这些概论的东西,才能在实践时写出优秀的代码,有不对或者不合理的地方欢迎在下面讨论;
- 什么是值类型和引用类型
c#中的引用类型和值类型的更多相关文章
- NET中的引用类型和值类型 zt
.NET中的类型分为值类型和引用类型,他们在内存布局,分配,相等性,赋值,存储以及一些其他的特性上有很多不同,这些不同将会直接影响到我们应用程序 的效率.本文视图对.NET 基础类型中的值类型和引用类 ...
- php中的引用类型和值类型
PHP中的四种简单类型和复杂类型array都是值类型.同类型间赋值传递的是值,即创建一个副本给新变量. 例如: $int1 = 123; $int2 = $int1;//直接传递的是值,只是做了一个叫 ...
- C#每天进步一点--引用类型和值类型
在刚参加工作面试时,我们经常会遇到有关值类型和引用类型的问题,你回答的怎么样直接影响你在别人心目中的印象,你回答的不好说明你对C#没有深入的了解学习,今天我带大家回顾下C#中的引用类型和值类型. CL ...
- C++中结构体与类的区别(结构不能被继承,默认是public,在堆栈中创建,是值类型,而类是引用类型)good
结构是一种用关键字struct声明的自定义数据类型.与类相似,也可以包含构造函数,常数,字段,方法,属性,索引器,运算符和嵌套类型等,不过,结构是值类型. 1.结构的构造函数和类的构造函数不同. a. ...
- C#中引用类型和值类型的区别,分别有哪些
C#的值类型包括:结构体(数值类型,bool型,用户定义的结构体),枚举,可空类型. C#的引用类型包括:数组,用户定义的类.接口.委托,object,字符串. 数组的元素,不管是引用类型还是值类型, ...
- 在C#的数据类型中,什么属于值类型,什么属于引用类型
转自原文 在C#的数据类型中,什么属于值类型,什么属于引用类型 类型:整数,浮点数,高精度浮点数,布尔,字符,结构,枚举引用类型:对象(Object),字符串,类,接口,委托,数组除了值类型和引用类型 ...
- C# 引用类型和值类型
C# 引用类型和值类型 CLR支持两种类型:引用类型和值类型. 1.引用类型 (1)内存必须从托管堆上分配: (2)堆上分配的每个对象都有一些额外成员(包括“类型对象指针”,“同步块索引”),这些成员 ...
- NET基础(4):引用类型和值类型
CLR支持两种类型:引用类型和值类型.虽然FCL的大多数类型都是引用类型,但程序员用的最多的还是引用类型,引用类型总是从托管堆分配,c#的new操作符返回对象内存地址-即指向对象数据的内存地址.使用引 ...
- 《CLR via C#》读书笔记--基元类型、引用类型和值类型
编程语言的基元类型 编译器直接支持的数据类型称为基元类型.基元类型直接映射到Framework类库中存在的类型.例如:C#中的int直接映射到System.Int32类型.下表给出了C#基元类型与对应 ...
随机推荐
- yii\bootstrap
yii\bootstrap\ButtonDropdown <?php echo yii\bootstrap\ButtonDropdown::widget([ 'label' => 'Act ...
- 【转载】redis.conf文件详解
转载地址:http://blog.csdn.net/zhutulang/article/details/51969760 Redis.conf文件可以在github上查看,下面是我整理的其中的配置项( ...
- Spring框架总结(十)
XML方式实现AOP编程 Xml实现aop编程: 1) 引入jar文件 [aop 相关jar, 4个] 2) 引入aop名称空间 3)aop 配置 * 配置切面类 (重复执行代码形成的类) * aop ...
- Python之set集合与collections系列
1>set集合:是一个无序且不重复的元素集合:访问速度快,解决了重复的问题: s2 = set(["che","liu","haha" ...
- javascript高级程序设计读书笔记----事件
DOM0级事件处理程序 传统处理方式,即讲一个函数赋值给一个事件处理程序属性. DOM2级事件处理程序 addEventListener()和removeHandler()两个方法用于指定和删 ...
- Raspberry Pi 3 安装 Lazarus 1.6.2(2017-02-09更新)
Raspberry Pi3 Lazarus 1.6.2 安装步骤如下: 安装环境:Raspbian Jessie, RPi3 1.安装subversion和unzip Sudo Apt-get upd ...
- MVC4 Model ValueProvider
1. NameValueCollectionValueProvider: ValueProvider 的数据容器一般具有类似字典的结构.NameValueCollection 表示一种 key 和va ...
- Verilog MIPS32 CPU(六)-- MDU
Verilog MIPS32 CPU(一)-- PC寄存器 Verilog MIPS32 CPU(二)-- Regfiles Verilog MIPS32 CPU(三)-- ALU Verilog M ...
- CLR via C# 读书笔记-27.计算限制的异步操作(上篇)
前言 学习这件事情是一个习惯,不能停...另外这篇已经看过两个月过去,但觉得有些事情不总结跟没做没啥区别,遂记下此文 1.CLR线程池基础 2.ThreadPool的简单使用练习 3.执行上下文 4. ...
- ASP .Net Core 2.0 修改默认端口
ASP .Net Core 的默认端口是5000,如果想在同一台服务器上运行多个实例,就不能都监听5000端口了,需要每一个实例都监听不同的端口.当然,如果您正在使用IIS或者Jexus来托管,可以不 ...