跟小静读CLR via C#(02)-基元类型、引用类型、值类型

简介:

跟小静读CLR via C#(02)-基元类型、引用类型、值类型

一、 基元类型

编译器能够直接支持的数据类型叫做基元类型。例如int, string等。基元类型和.NET框架类库FCL存在着直接的映射关系。

string和String

面试的时候曾经被问到过这个问题,C#中的基元类型string实际上对应了System.String(FCL)类型,所以两者使用的时候没有什么不同。

类型转换

编译器能够在基元类型之间进行显式或隐式转换。如果转换是安全的,也就是转换过程不会造成数据丢失,则可以直接采用隐式转换。如果是不安全的,则必须采用显式转换。

Int32 a=5;

Int64 b=a;

Int32 c=(Int32)b;

二、 引用类型和值类型

引用类型和值类型的区别:

引用类型

值类型

从托管堆中分配

从线程的堆栈中分配

对象考虑垃圾回收机制

不考虑垃圾回收机制

所有类都是引用

结构或枚举都是值类型

继承自System.ValueType

只有装箱形式

有两种形式:装箱和未装箱

可以继承和派生

不能作为基类,不能有虚方法

引用类型变量初始化时默认为null

初始化时默认为0值

复制时只拷贝内存地址

复制时“字段对字段”的拷贝

   

结构体直接继承自System.ValueType;而枚举直接继承自System.Enum, Enum类又直接继承自System.ValueType。

下面通过例子看一下他们的区别:

首先定义类和结构体:

class SomeRef { public Int32 x; }

struct SomeVal { public Int32 x; }

SomeRef r1 = new SomeRef(); // 分配到堆

SomeVal v1 = new SomeVal(); // 分配到栈

r1.x =5; // 所引用的堆空间内数据修改

v1.x =5; // 直接在栈上复赋值

Console.WriteLine(r1.x); // "5"

Console.WriteLine(v1.x); // "5"

SomeRef r2 = r1; //只把指针复制给了r2

SomeVal v2 = v1; // 在栈上分配空间,并且将变量内容进行复制

r1.x = 8; // r1指向(也是r2指向)的内容修改

v1.x = 9; // 只修改v1内容,v2内容不会受影响

Console.WriteLine(r1.x); // "8"

Console.WriteLine(r2.x); // "8"

Console.WriteLine(v1.x); // "9"

Console.WriteLine(v2.x); // "5"

看看下图的内存分配情况,就一目了然了。

image

三、 值类型的装箱与拆箱

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首先被拆箱然后再装箱为引用类型。

我们看一下它的IL代码:

QQ截图20111021101944

字符串连接实际上调用的是Concat方法,该方法的参数要求是object,因此各参数都要转换成object类型。

 

优化:

Console.WriteLine(v.ToString()+”,”+o);//装箱0次

我们要尽量少的进行值类型的装箱拆箱操作,以提高程序性能。



    本文转自 陈敬(Cathy) 博客园博客,原文链接:http://www.cnblogs.com/janes/archive/2011/07/04/2097540.html,如需转载请自行联系原作者



相关文章
|
3月前
|
存储 安全 编译器
C# 11.0中的泛型属性:类型安全的新篇章
【1月更文挑战第23天】C# 11.0引入了泛型属性的概念,这一新特性为开发者提供了更高级别的类型安全性和灵活性。本文将详细探讨C# 11.0中泛型属性的工作原理、使用场景以及它们对现有编程模式的改进。通过深入了解泛型属性,开发者将能够编写更加健壮、可维护的代码,并充分利用C#语言的最新发展。
|
7月前
|
编译器 C#
C#之十七 局部类型
C#之十七 局部类型
17 0
|
1月前
|
存储 C# 开发者
C#变量类型
C#变量类型
19 0
|
7月前
|
存储 C# C语言
C# OOP之五 深入理解值类型和引用类型
C# OOP之五 深入理解值类型和引用类型
27 0
|
3月前
|
开发框架 .NET 编译器
C# 9.0中的目标类型新表达式:类型推断的又一进步
【1月更文挑战第16天】C# 9.0引入了目标类型新表达式,这是类型推断功能的一个重要扩展。通过目标类型新表达式,开发者在创建对象时可以省略类型名称,编译器会根据上下文自动推断所需类型。这一特性不仅简化了代码编写,还提高了代码的可读性和维护性。本文将详细介绍目标类型新表达式的语法、使用场景及其对C#编程的影响。
|
3月前
|
存储 C# 容器
掌握 C# 变量:在代码中声明、初始化和使用不同类型的综合指南
变量是用于存储数据值的容器。 在 C# 中,有不同类型的变量(用不同的关键字定义),例如: int - 存储整数(没有小数点的整数),如 123 或 -123 double - 存储浮点数,有小数点,如 19.99 或 -19.99 char - 存储单个字符,如 'a' 或 'B'。Char 值用单引号括起来 string - 存储文本,如 "Hello World"。String 值用双引号括起来 bool - 存储具有两个状态的值:true 或 false
37 2
|
3月前
|
安全 编译器 C#
C#中的可空引用类型:减少空引用异常的利器
【1月更文挑战第9天】C# 8.0中引入的可空引用类型特性,它通过在编译时提供更精确的静态分析,帮助开发者减少运行时的空引用异常。文章详细阐述了可空引用类型的工作原理、如何配置项目以使用此特性,以及在实际编码中如何利用可空引用类型提升代码的健壮性和可读性。
|
3月前
|
存储 安全 算法
C# 泛型:类型参数化的强大工具
【1月更文挑战第7天】本文将深入探讨C#语言中的泛型编程,包括泛型的定义、用途、优势以及实际应用。通过类型参数化,泛型允许开发者编写更加灵活且可重用的代码,同时提高程序的类型安全性和性能。本文将通过示例代码和详细解释,帮助读者更好地理解泛型在C#中的重要性和实用性。
|
4月前
|
存储 Java C#
【从Java转C#】第七章:运算符和类型强制转换
【从Java转C#】第七章:运算符和类型强制转换
|
4月前
|
Java 编译器 C#
【从Java转C#】第三章:对象和类型
【从Java转C#】第三章:对象和类型