05 基元类型、引用类型和值类型

基元类型

书上一开头就说了一个概念

编译器直接支持的数据类型称为基元类型(primitive type)。

以下是基元类型.

C# Primitive Typ FCL Type CLS-Compliant
sbyte System.SBte NO
byte System.Byte YES
short System.Int16 YES
ushort System.UInt16 NO
int System.Int32 YES
uint System.UInt32 NO
long System.Int64 YES
ulong System.UInt64 NO
char System.Char YES
float System.Single YES
double System.Double YES
decimal System.Decimal YES
object System.Object YES
string System.String YES

和基元类型对应的 就是Class 或者 复杂类型。

这些经常使用的基元类型,所以有一些简化的语法去操作他.

1 int i = 0;
2 Console.WriteLine(i);
3 Console.ReadKey();
  .entrypoint
  // 代码大小       15 (0xf)
  .maxstack  1
  .locals init ([0] int32 i)
  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  IL_0002:  ldloc.0
  IL_0003:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0008:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  IL_000d:  pop
  IL_000e:  ret

IL_0000  将所提供的 int32 类型的值作为 int32 推送到计算堆栈上。

IL_0001  从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中

IL_0002  将索引 0 处的局部变量加载到计算堆栈上。

同样。

1 System.Int32 i = new System.Int32();
2 Console.WriteLine(i);
3 Console.ReadKey();

生成的IL也一样。 就不贴了.

所以你可以认为

1 int
2 string

就是语法糖. 其实最后就是生成的一样的代码.

为了方便你编码.

比如还有

1 int i = 0;
2 Int64 l = i;
3 Console.WriteLine(l);
4 Console.ReadKey();
1 int i = 0;
2 Int64 l = (Int64)i;
3 Console.WriteLine(l);
4 Console.ReadKey();
 1 .maxstack  1
 2   .locals init ([0] int32 i,
 3            [1] int64 l)
 4   IL_0000:  ldc.i4.0
 5   IL_0001:  stloc.0
 6   IL_0002:  ldloc.0
 7   IL_0003:  conv.i8
 8   IL_0004:  stloc.1
 9   IL_0005:  ldloc.1
10   IL_0006:  call       void [mscorlib]System.Console::WriteLine(int64)
11   IL_000b:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
12   IL_0010:  pop
13   IL_0011:  ret

隐式转换。 不用你再去写什么 (int64)或者 Convert.ToInt64() 之类的.

不过他也有限制。只有在确认不会发生数据丢失的情况下,才允许隐式转换.
比如 从小的转向大的. int32 转 int64
否则

错误    3    无法将类型“long”隐式转换为“int”。存在一个显式转换(是否缺少强制转换?)       45    24    EFTest

本书作者不建议使用这些语法糖。

理由无外物 会让你不清楚 究竟做了什么 ,和其他编程语言标准不一致等等。

我当然完全无视这些.

Checked & UnChecked

checked 和 unchecked关键字用来限定检查或者不检查数学运算溢出的

如果使用了checked发生数学运算溢出时会抛出OverflowException

如果使用了unchecked则不会检查溢出,算错了也不会报错。

首先在VS中,Checked 应该是默认打开的

生成就已经报错了.

1   int a = unchecked( int.MaxValue * 2);

a = -2

unchecked  溢出后会生成一个特定的数。 具体算法不清楚. 特定场景比如你就需要一个不重复的数值。 那就可以用他.

对于这两个指令。应用场景实在太小了. 就不深入研究了.

引用类型和值类型

1. 什么是值类型

System.ValueType 派生的都是值类型

2. 什么是引用类型

在文档中查看一个类型时,任何称为“类”的类型都是引用类型

引用类型

内存必须从托管堆上分配

堆上分配的每个对象都有一些额外的成员,这些成员必须初始化

对象中的其他字节(为字段而设)总是设为零

从托管堆上分配一个对象时,可能强制执行一次垃圾收集操作

值类型

值类型的实例一般在线程栈上分配
在代表值类型实例的一个变量中,并不包含一个指向实例的指针。相反,变量中包含了实例本身的字段。
由于变量已经包含了实例的字段,所以为了操作实例中的字段,不再需要提领一个指针。
值类型的实例不受垃圾回收器的控制

书中个的理由是

如果所有类型都是引用类型,应用程序的性能将显著下降。
设想假如每次使用一个Int32值时,都进行一次内存分配,性能会受到多么大的影响!
为了提升简单的、常用的类型的性能,CLR提供了名为“值类型”的轻量级类型

从数据结构来说.

栈就是一个后进先出的简单结构.

堆是一种是经过排序的树形结构.

复杂度不一样. 分配的速度自然也不一样.

栈中的内存通过寄存器定位

堆在寄存器中就一个地址,再通过地址去内存里面找理论上也慢一点儿.

不过在现在这么屌的CPU,还有没有区别就不知道了. 或许已经达到可以忽略的地步了.

关于值类型引用类型的分配.

网上很多是这样说的。

值类型                栈

引用类型中的值类型     GC Heap

引用类型 < 85000byte     GC Heap

引用类型 > 85000byte     Large Object Heap

 值类型 和 引用类型的区别

 1 public struct ccc
 2 {
 3
 4 }
 5
 6 public class bbb : aaa
 7 {
 8
 9 }
10
11 var c1 = new ccc();
12 var c2 = c1;
13
14 var b1 = new bbb();
15 var b2 = b1;

查看一下内存地址

c1  0x0679EF28
c2  0x0679EF24

b1  0x021E4AAC
b2  0x021E4AAC

引用类型中的值类型

1 var a = new aaa();
2 var b = a;
3
4 a.c++;
5
6 Console.WriteLine(a.c);
7 Console.WriteLine(b.c);

结果是 1,1

a 和 b 的内存地址都是 0x02314AAC

至于 是在GC Heap 还是 Large Object Heap

这个我没有想到办法验证.

注意事项

本书说了一堆注意事项。 其实稍微多用一点儿 自己就明白了.

唯一要注意的是传递。

 1 public struct ccc
 2 {
 3
 4 }
 5
 6  var a = new ccc();
 7  test(a);
 8
 9  public static void test(ccc i)
10  {
11
12      Console.WriteLine(i.GetType());
13  }

看内存地址

a  0x0652E860

i   0x0652E850

因为值类型都在线程栈中. 这样的传递都会复制一次.

特别是面向对象结构一层一层的. 如果 传递的值类型过大. 必定会对性能有一定影响.

当然也有特殊的int,地址是一样的.

装箱和拆箱

其实就是值类型和引用类型的相互转换

值类型转引用类型 装箱.

引用类型转值类型你个 拆箱.

我没有找到测试方法。比如他究竟怎么运作的。 从栈怎么复制过去的。

所以就引用书中的说法。

值类型转引用类型。

  1. 在堆中分配内存(包括同步索引之类的必须的)
  2. 复制到堆
  3. 返回堆中的地址

引用类型转值类型

  1. 找到堆地址
  2. 复制到栈

书中特别提了一句

拆箱其实就是获取一个指针的过程,该指针指向包含在一个对象中的原始值类型(数据字段)

我并不是特别理解这一句话。

如果堆中保留了 原始值类型。 可是他依然在堆中。 所以不管怎么样都需要有一次复制操作。

注意事项

1.如果包含了“对已装箱值类型实例的引用”的变量为null,就抛出一个NullReferenceException异常。
2.如果引用指向的对象不是所期待的值类型的一个已装箱实例,就抛出一个InvalidCastException异常。

1 public static void Main() {
2 Int32 x = 5;
3 Object o = x;
4 //对x进行装箱,o引用已装箱的对象
5 Int16 y = (Int16) o;
6 //抛出一个InvalidCastException异常
7 }
Int32 x = 5;
Object o = x;
Int64 y = (Int64)o;

拆箱必须和原有的类型一致,不管大小都不行.

时间: 2024-10-29 19:05:48

05 基元类型、引用类型和值类型的相关文章

[CLR via C#]引用类型和值类型

一.引用类型与值类型的区别 CLR支持两种类型:引用类型和值类型.引用类型总是从托管堆上分配的,C#的new操作符会返回对象的内存地址.使用引用类型时,必须注意到一些性能问题. 1)内存必须从托管堆上分配. 2)堆上分配的每个对象都有一些额外的成员(类型对象指针和同步索引块),这些成员必须初始化. 3)对象中的其他字节(为字段而设)总是设为零. 4)从托管城市化上分配一个对象时,可能强制执行一次垃圾收集操作. 为了提升简单的.常用的类型的性能,CLR提供了名为"值类型"的轻量级类型.值

5.基元类型、引用类型和值类型

5.1 基远类型 编译器直接支持的数据类型称为基远类型(primitive type). 以下4行到吗生成完全相同的IL int a = 0; //最方便的语法 System.Int32 b = 0; //方便的语法 int c = new int(); //不方便的语法 System.Int32 d = new System.Int32(); //最不方便的语法 C#基元类型与对应的FCL类型 C#中的基元类型 FCL类型 是否与CLS兼容 描述 sbyte System.SByte N 有符

CLR via C#深解笔记三 - 基元类型、引用类型和值类型 | 类型和成员基础 | 常量和字段

编程语言的基元类型 某些数据类型如此常用,以至于许多编译器允许代码以简化的语法来操纵它们. System.Int32 a = new System.Int32();  // a = 0 a = 1; 等价于: int a = 1; 这种语法不仅增强了代码的可读性,其生成的IL代码与使用System.Int32时生成的IL代码是完全一致的. 编译器直接支持的数据类型称为基元类型(primitive type).基元类型直接映射到Framework类库(FCL)中存在的类型.如C#中,int直接映射

CLR VIA C#: 基元类型、 引用类型 和 值类型

一.基元类型 . 引用类型 和 值类型的区别: 1.基元类型(primitive type):编译器直接支持的数据类型: 基元类型 直接映射到 FCL 中存在的类型. C# 小写是基元类型,例如:string ,  大写是FCL类型,例如String,  基元类型直接映射到FCL类型,所以这两者之间没有区别,一模一样,不用纠结使用哪种方式了. 支持 直接使用FCL类型, 不使用基元类型, Float 对应 Single FCL类型: dynamic 对应  System.Object FCL类型

第五章 基元类型、引用类型 和 值类型

1. 概述 本章讨论MS.NET Framework开发人员经常接触到的各种类型. 2. 名词解释 ① 基元类型:编译器直接支持的数据类型. ② 装箱:将一个值类型转换成一个引用类型. ③ 拆箱:获取已装箱的对象中的各个字段的地址. 3. 主要内容 3.1 编程语言的基元类型 作者建议开发人员开发中使用FCL类型名称,不要使用简化的基元类型名称.原因如下: ① 简化名称的映射容易混淆. ② 不同的编程语言,规则不一. ③ 影响代码的阅读. ④ 时间长了容易忘掉多语言环境的支持. checked

第5章 基元类型、引用类型和值类型

5.1编程语言的基元类型 编译器(Compiler)直接支持的数据类型称为基元类型(primitive type). 我希望编译器根本不要提供基元类型名称,强制开发人员使用FCL(Framework类库)类型名称: 许多开发人员都困惑于到底应该使用string还是String.由于C#的string直接映射到System.String,所以两者是没有区别的.int始终映射到System.Int32,所以不管在什么操作系统上运行,代表的都是32位整数. 5.2引用类型和值类型 虽然FCL中大多数类

引用类型和值类型

CLR支持两种类型:引用类型和值类型. 虽然FCL中大多数都是引用类型,但开发人员用的最多的还是值类型.引用类型总是在托管堆上分配的,C#的new操作符会返回对象的内存地址——也就是指向对象数据的内存地址. 使用引用类型必须注意到一些性能问题,首先考虑一下事实: 1)内存必须从托管堆上分配. 2)对上分配的每个对象都有一些额外的成员(比如前面提到过得"类型对象指针"和"同步块索引"),这些成员必须初始化. 3)对象中的其他字节(为字段而设)总是设为零. 4)从托管堆

NET基础(4):引用类型和值类型

CLR支持两种类型:引用类型和值类型.虽然FCL的大多数类型都是引用类型,但程序员用的最多的还是引用类型,引用类型总是从托管堆分配,c#的new操作符返回对象内存地址-即指向对象数据的内存地址.使用引用类型必须注意性能问题.首先要认清楚以下4个方面: 1.内存必须从托管堆分配. 2.堆上分配的每个对象都有一些额外的成员,这些成员必须初始化. 3.对象中的其它字节(为字段而设)总是设为零. 4.从托管堆分配对象时,可能强制执行一次垃圾回收. 如果所有类型都是引用类型,应用程序的性能将会显著下降.设

【转】c#引用类型与值类型的区别大盘点

解析:CLR支持两种类型:值类型和引用类型.用Jeffrey Richter(<CLR via C#>作者)的话来说,“不理解引用类型和值类型区别的程序员将会把代码引入诡异的陷阱和诸多性能问题”.这就要求我们正确理解和使用值类型和引用类型. 值类型包括C#的基本类型(用关键字int.char.float等来声明),结构(用struct关键字声明的类型),枚举(用enum关键字声明的类型):而引用类型包括类(用class关键字声明的类型)和委托(用delegate关键字声明的特殊类).C#中的每