【CLR Via C#】第5章 基元类型、引用类型、值类型

时间:2023-03-08 15:54:56

  第二遍看这本书,决定记录一下加深印象。

值类型可以存储在堆和栈上,它是局部变量时存储在栈上,如果值类型是作为类的一个属性,那么就会存储在堆上;

引用类型有两块内存,一块存储引用地址(栈上),一块存储实际的对象(堆上)。

1,基元类型

  什么事基元类型?基元类型是直接映射到FrameWork类库(FCL)中存在的类型,编译器直接支持的数据类型。比如int直接映射到System.Int32类型,就像是添加了using应用:using sbyte=System.SByte.

C#基元类型 FCL类型 说明
sbyte System.SByte 有符号8位
byte System.Byte 无符号8位
short System.Int16 有符号16位
ushort System.UInt16 无符号16位
int System.Int32 有符号32位
uint System.UInt32 无符号32位
long Syetem.Int64 有符号64位
ulong System.Int64 无符号64位
char System.Char 16位Unicode字符
float System.Single 32位浮点值,即带小数
double System.Double 64位浮点值
bool System.Boolean True/False
decimal  [英]'desɪml System.Decimal 128位高精度浮点值
string System.String 字符数组
object System.Object 所有类型的基类
dynamic System.Object

对于CLR,dynamic和Object完全一致

  基元类型只有在数量级别小转大的时候可以隐式转换,数量级别大转小的时候必须显示的转换,如下:

  Int32 i=1;   Int64 l=i;  Single s=i;  -----隐式转换

  Byte b=(Byte)i; Int16 v=(Int16)i;  -----显示转换

  因为数量级别大转小可能会造成内存溢出,需要用到checked喝unchecked,

    使用checked发生溢出时会抛出OverFlowEception异常;

    unchecked允许发生溢出;

2 引用类型和值类型

  1,为什么会有引用类型和值类型

      因为引用类型每一次使用的时候会进行一次内存分配,非常影响程序性能。值类型一般在线程栈上分配,值类型不受垃圾回收器的控制,缓解了托管堆中的压力,减少了一个应用程序在其生存周期内需要进行回收的次数。值类型用 struct来声明。

  2.什么时候用值类型

      1,类型十分简单,成员值不会被修改,建议标记为readonly。

      2,不需要从其他任何类型继承。

      3,也不会派生出其他类型。

      4,类型实例占用小,小于16个字节,大于16个字节时不作为参数传递,也不会被方法返回。

      老赵有一篇经典的struct用法的文章:http://blog.zhaojie.me/2013/04/dont-go-half-way-of-preventing-boxing.html

  3.值类型与引用类型的不同

值类型 引用类型
有2中表示形式:未装箱和已装箱 总是处于已装箱状态

从System.ValueType派生,由于性能问题,定义值类型时候

需要重写Equals和GetHashCode方法

从System.Object派生

不能将值类型作为基类,所以不能写虚方法,不能是抽象方法

,所有方法都隐式地为密封方法

可以继承和派生
所有成员初始化为0(可空除外) 默认值为Null,调用抛出NullReferenceException异常
值类型赋值时会逐字段赋值 引用类型赋值时赋给内存指针
值类型赋值后自成一体,操作不会受影响 引用类型引用的是同一对象,操作会受影响

值类型不在堆上分配,一旦实例方法不处于活动状态,分配的

存储就会被释放,不需要考虑垃圾回收

由GC回收,需要考虑垃圾回收机制。

  4,值类型的拆箱和装箱     

1. 装箱过程?

装箱:将值类型转换为引用类型。当我们把值类型参数传递给需要引用类型参数的方法时,会自动进行装箱操作。过程如下:

        • 从托管堆为要生成的引用类型分配大小。大小为:值类型实例本身的大小+额外空间(方法表指针和SyncBlockIndex)。
        • 将值类型字段拷贝到刚刚分配的内存中。
        • 返回托管堆中新分配内存的地址。也就是指向对象的引用。

2. 拆箱过程?

拆箱:获取指向对象中包含的值类型部分的指针。一般拆箱之后会进行字段拷贝操作,两个操作加起来才是真正与装箱互反的操作。过程如下:

      • 如果引用为Null,则抛出NullReferenceException异常。
      • 如果引用对象不是一个期望值类型的已装箱对象,会抛出InvalidCastException异常。
      • 返回一个指向包含在已装箱对象中值类型部分的指针。

3. 实例

      • 拆箱的转型结果必须是它原来未装箱时的类型。

public static void Main() {

Int32 x = 5;

Object o = x; // 装箱

Int16 y = (Int16) o; // 拆箱,抛出InvalidCastException异常

}

修正:Int16 z=(Int16)(Int32)o;//拆箱成功

    • 这段代码进行了几次装箱?

public static void Main() {

Int32 v = 5; // 创建值变量

Object o = v; // 装箱

v = 123; // Changes the unboxed value to 123

Console.WriteLine(v + ", " + (Int32) o); // Displays "123, 5" ,装箱两次

}

上面的代码进行了3次装箱,最后一行中v被装箱为引用类型,o首先被拆箱然后再装箱为引用类型。

这一段来自小静:http://www.cnblogs.com/janes/archive/2011/07/04/2097540.html

5,dynamic基元类型

      这一段发布的时候自己丢失了,运行时绑定,由dynamic对象的类型来实际决定具体执行的操作!

 using System;

 internal static class DynamicDemo{
public static void Main(){
for(int i=;i<;i++){
dynamic arg=i==?(dynamic):(dynamic)"A";
dynamic result=Plus(arg);
M(result); //这里会自动根据dynamic是什么类型调用相应的方法。
}
} private static dynamic Plus(dynamic arg){
return arg+arg;
}
privaye static void M(Int32 a){
Console.WriteLine("M(Int32)"+a);
} privaye static void M(String a){
Console.WriteLine("M(String)"+a);
}
}