|
装箱 就是把“值类型”转换成“引用类型”(Object);
拆箱 就是把“引用类型”转换成“值类型”;
int i = 3 ;
object o = i ;
object b = (object)i ;
int j = (int) o ;
int k = b ;
|
拆箱 的操作包括
1,检查对象实例,以却确保它是给定值类型的装箱值。
2,将该值从实例复制到值类型变量中。
下面来看看这个例子:
|
1
2
3
|
int i=0;
System.Object obj=i;
Console.WriteLine(i+","+(int)obj);
|
其中共发生了3次装箱和一次拆箱!^_^,看出来了吧?! 第一次是将i装箱,第2次是输出的时候将i转换成string类型,而string类型为引用类型,即又是装箱,第三次装箱就是(int)obj的转换成string类型,装箱! 拆箱就是(int)obj,将obj拆箱!!
https://blog.csdn.net/wuan584974722/article/details/81902904
一,c#中的值类型和引用类型
众所周知在c#中有两种基本类型,它们分别是值类型和引用类型;而每种类型都可以细分为如下类型:
![]()
- 什么是值类型和引用类型
- 什么是值类型:
- 进一步研究文档,你会发现所有的结构都是抽象类型System.ValueType的直接派生类,而System.ValueType本身又是直接从System.Object派生的。根据定义所知,所有的值类型都必须从System.ValueType派生,所有的枚举都从System.Enum抽象类派生,而后者又从System.ValueType派生。
- 所有的值类型都是隐式密封的(sealed),目的是防止其他任何类型从值类型进行派生。
- 什么是引用类型:
- 区别和性能
- 区别:
- 值类型通常被人们称为轻量级的类型,因为在大多数情况下,值类型的的实例都分配在线程栈中,因此它不受垃圾回收的控制,缓解了托管堆中的压力,减少了应用程序的垃圾回收的次数,提高性能。
- 所有的引用类型的实例都分配在托管堆上,c#中new操作符会返回一个内存地址指向当前的对象。所以当你在创建个一个引用类型实例的时候,你必须要考虑以下问题:
- 内存是在托管堆上分配的
- 在分配每一个对象时都会包含一些额外的成员(类型对象指针,同步块索引),这些成员必须初始化
- 对象中的其他字节总是设为零
- 在分配对象时,可能会进行一次垃圾回收操作(如果托管堆上的内存不够分配一次对象时)
- 性能:
- 在设计一个应用程序时,如果都是应用类型,那么应用程序的性能将显著下降,因为这会加大托管堆的压力,增加垃圾回收的次数。
- 虽然值类型是一个轻量级的类型,但是如果大量的使用值类型的话,也会有损应用程序的性能(例如下面要讲的装箱和拆箱操作,传递实例较大的值类型,或者返回较大的值类型实例)。
- 由于值类型实例的值是自己本身,而引用类型的实例的值是一个引用,所以如果将一个值类型的变量赋值给另一个值类型的变量,会执行一次逐字段的复制,将引用类型的变量赋值给另一个引用类型的变量时,只需要复制内存地址,所以在对大对象进行赋值时要避免使用值类型。例如下面的代码
-
1 class SomRef
2 {
3 public int x;
4 }
5 struct SomeVal {
6
7 public int x;
8
9 }
10
11 class Program {
12
13 static void ValueTypeDemo() {
14
15 SomRef r1 = new SomRef();//在堆上分配
16
17 SomeVal v1 = new SomeVal();//在栈上分配
18
19 r1.x = 5;//提领指针
20
21 v1.x = 5;//在栈上修改
22
23 SomRef r2 = r1;//只复制引用(指针)
24
25 SomeVal v2 = v1;//在栈上分配并复制成员
26
27 }
28
29 }
-
- 常见误区
-
- 引用类型分配在托管堆上,值类型分配在线程栈上:其实这种说法的前半部分是对的,后半部分是错的。因为变量的值在它声明的位置存储的,所以假如某一个引用类型中有一个值类型的变量, 那么该变量的值总是和该引用类型的对象的其它数据在一起,也就是分配在堆上。(只有局部变量(方法内部声明的变量)和方法的参数在栈上)
- 结构是轻量级的类:这种错误的信息主要是因为有人认为值类型不应该有方法或者其它有意义的行为-它们应该作为简单的数据转移来使用,所以很多人分不清DateTime到底是值类型还是引用类型。
- 对象在c#中默认的是用引用传递的:其实在调用方法的时候,参数值(对象的一个引用)是以传值得方式传递的,如果你想以引用方式传递的话,可以使用ref或者out关键字。
二,值类型的装箱和拆箱操作
1 int i = 5;
2
3 object o = i;
4
5 int j = (int)o;
6
7 Int16 y=(Int16)o;
- 什么是装箱,什么是拆箱
- 什么是装箱:所谓装箱就是将值类型转化为引用类型的过程(例如上面代码的第2行),在装箱时,你需要知道编译器内部都干了什么事:
- 在托管堆中分配好内存,分配的内存量是值类型的各个字段需要的内存量加上托管堆上所以对象的两个额外成员(类型对象指针,同步块索引)需要的内存量
- 值类型的字段复制到新分配的堆内存中
- 返回对象的地址,这个地址就是这个对象的引用
- 什么是装箱:将已装箱的值类型实例(此时它已经是引用类型了)转化成值类型的过程(例如上面代码的第3行),注意:拆箱不是直接将装箱过程倒过来,拆箱的代价比装箱要低的多,拆箱其实就是获取一个指针的过程。一个已装箱的实例在拆箱时,编译器在内部都干了下面这些事:
- 如果包含了“对已装箱类型的实例引用”的变量为null时,会抛出一个NullReferenceException异常。
- 如果引用指向的对象不是所期待的值类型的一个已装箱实例,会抛出一个InvalidCastException异常(例如上面代码的第4行)。
- 它们在什么情况下发生,以及如何避免
-
1 static void Main(string[] args)
2 {
3
4 int v = 5;
5
6 object o = v;
7
8 v = 123;
9
10 Console.WriteLine(v+","+(int)o);
11
12 }
通过上面的分析我们已经知道了,装箱和拆箱/复制操作会对应用程序的速度和内存消耗产生不利的影响(例如消耗内存,增加垃圾回收次数,复制操作),所以我们应该注意编译器在什么时候会生成代码来自动这些操作,并尝试手写这些代码,尽量避免自动生成代码的情况。
三,泛型的出现(本节只简单介绍泛型对装箱和拆箱所起的作用,关于泛型的具体细节请参考下一篇文章)
四,在设计时如何选择类和结构体
在面试的时候,我们经常被问的一个问题(还有另外一个问题,如何选择抽象类和接口,下次我会单独聊聊这个问题),下面我们来聊聊在设计时应该如何选择结构体和类
-
- 什么是结构体
- 结构体是一种特殊的值类型,所以它拥有值类型所以的特权(实例一般分配在线程栈上)和限制(不能被派生,所以没有 abstract 和 sealed,未装箱的实例不能进行线程同步的访问)。
- 什么情况下选择结构体,什么情况下选择类
- 在大多数的情况下,都应该选择类,除非满足以下情况,才考虑选择结构体:
- 类型具有基元类型的行为
- 类型不需要从其它任何类型继承
- 类型也不会派生出任何其它类型
- 类型的实例较小(约为16字节或者更小)
- 类型的实例较大,但是不作为方法的参数传递,也不作为方法的返回值。
都说程序是一门注重实践的学科,但是也只有熟悉理解了这些概论的东西,才能在实践时写出优秀的代码,有不对或者不合理的地方欢迎在下面讨论;
https://www.cnblogs.com/bakuhert/articles/5878086.html
什么是值类型,什么是引用类型
概念:值类型直接存储其值,而引用类型存储对其值的引用。部署:托管堆上部署了所有引用类型。
引用类型:基类为Objcet
值类型:均隐式派生自System.ValueType:
|
值类型:
|
byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。
|
|
引用类型:
|
string 和 class统称为引用类型。
|
- 值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
- 引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
- 值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。
- 引用类型的对象总是在进程堆中分配(动态分配)。
来源:https://www.cnblogs.com/zjtao/p/11345442.html |