基元类型&引用类型&值类型

作者:追风剑情 发布于:2021-9-17 13:42 分类:C#

一、基元类型

编译器直接支持的数据类型称为基元类型(primitive type)。基元类型直接映射到Framework类库(FCL)中存在的类型。例如,C#的int直接映射到System.Int32类型。

C#基元类型与对应的FCL类型
C#基元类型 FCL类型 符合CLS 说明
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
System.Int64 有符号64位值
ulong System.UInt64 无符号64位值
char System.Char 16位Unicode字符(char不像在非托管C++中那样代表一个8位值)
float System.Single IEEE 32位浮点值
double System.Double IEEE 64位浮点值
bool System.Boolean true/false值
decimal System.Decimal 128位高精度浮点值,常用于不容许舍入误差的金融计算。128位中,1位是符号,96位是值本身(N),8位是比例因子(k)。decimal实际值是±N×10k,其中-28≤k≤0。其余位没有使用。decimal值的处理速度慢于其他基元类型。由于没有相应的 IL 指令来处理 decimal 值,所以 checked 和 unchecked 对 decimal 是无效的。也就是说decimal值运算溢出时总会抛出OverflowException异常。
string System.String 字符数组
object System.Object 所有类型的基类型
dynamic System.Object 对于CLR,dynamic和object完全一致。但C#编译器允许使用简单的语法让dynamic变量参与动态调度。
System.Numerics.BigInteger 在内部使用UInt32数组来表示任意大的整数,它的值没有上限和下限。但如果值太大,没有足够多的内存来改变数组大小,对 BigInteger 的运算可能抛出 OutOfMemoryException 异常。

只要是符合公共语言规范(CLS)的类型,其他语言都提供了类似的基元类型。但是,不符合CLS的类型语言就不一定要支持了。

算术运算溢出检测

UInt32 invalid = unchecked((UInt32) (-1)); // OK,不执行溢出检测(C#默认行为)

Byte b = 100;
b = checked((Byte) (b + 200)); // 抛出 OverflowException 异常

checked {
    //对代码块进行运算溢出检测
}

unchecked {
    //对代码块不进行运算溢出检测
}

如果要对所有代码执行溢出检测,可以使用编译开关 /checked+ (开启后会降低程序性能)。对所有代码关闭运算溢出检测 /checked-

Microsoft Visual Studio 中更改Checked设置(项目属性->生成->高级->勾选检查算术溢出)
11111.png

二、引用类型和值类型

引用类型总是从托管堆分配,C#的new操作符返回对象内存地址。值类型的实例一般在线程栈上分配。值类型的实例不受垃圾回收器的控制。因此,值类型的使用缓解了托管堆的压力,并减少了应用程序生存期内垃圾回收次数。所有类对象都是引用类型,所有结构和枚举都是值类型。所有值类型都继承 System.ValueType,所有枚举类型都继承 System.Enum,所有值类型都隐式密封无法被继承。

在定义结构时,可以使用StructLayoutAttribute特性控制字段布局。
LayoutKind.Auto: 让CLR自动排列字段。
LayoutKind.Sequential: 让CLR保持你的字段布局。
LayoutKind.Explicit: 利用偏移量在内存中显式排列字段。用来模拟C/C++中的union

using System;
using System.Runtime.InteropServices;
// 让CLR自动排列字段以增加这个值类型的性能
[StructLayout(LayoutKind.Auto)]
internal struct SomeValType {
   private readonly Byte m_b;
   private readonly Int16 m_x;
   ...
}
using System;
using System.Runtime.InteropServices;
// 开发人员显式排列这个值类型的字段
[StructLayout(LayoutKind.Explicit)]
internal struct SomeValType {
   //m_b和m_x字段在该类型的实例中相互重叠
   [FieldOffset(0)]
   private readonly Byte m_b;
   [FieldOffset(0)]
   private readonly Int16 m_x;
}

三、值类型的装箱和拆箱

[官方文档] 装箱和取消装箱

官方文档解释:
      装箱是将值类型转换为 object 类型或由此值类型实现的任何接口类型的过程。 常见语言运行时 (CLR) 对值类型进行装箱时,会将值包装在 System.Object 实例中并将其存储在托管堆中。 取消装箱将从对象中提取值类型。 装箱是隐式的;取消装箱是显式的。 装箱和取消装箱的概念是类型系统 C# 统一视图的基础,其中任一类型的值都被视为一个对象。

将值类型赋给引用类型变量时会进行装箱操作,反之,将装箱后的对象赋给值类型变量时会进行拆箱操作。拆箱的代价比装箱低得多。拆箱是一个获取指针的过程,没有字节在内存中复制。

值类型装箱操作
1、在托管堆中分配内存。分配的内存量是值类型各字段所需的内存量,还要加上托管堆所有对象都有的两个额外成员(类型对象指针和同步块索引)所需的内存量。
2、值类型的字段复制到新分配的堆内存。
3、返回对象地址。现在该地址是对象引用;值类型成了引用类型。

泛型集合类(例如,System.Collections.Generic.List),在对值类型操作时,不会进行装箱和拆箱操作。


重要提示  可用 ILDasm.exe 这样的工具查看方法的 IL 代码,观察 IL 指令 box 都在哪些地方出现。

由于未装箱值类型没有同步块索引,所以不能使用 System.Threading.Monitor 类型的方法(或者 C# lock 语句)让多个线程同步对实例的访问。


四、对象哈希码

如果你定义的类型重写了 Equals 方法,还应该重写 GetHashCode 方法。类型定义 Equals 之所以还要定义 GetHashCode ,是由于 System.Collections.Hashtable 类型、System.Collections.Generic.Dictionary 类型以及其他一些集合的实现,要求两个对象必须具有相同哈希码才被视为相等。所以,重写 Equals 就必须重写 GetHashCode,确保相等性算法和对象哈希码算法一致。包含相同值的不同对象应返回相同哈希码

五、dynamic 基元类型

dynamic本质上是object类型。

dynamic d = 123; // 从 Int32 隐式转型为 dynamic(装箱)
Int32 n = d; // 从 dynamic 隐式转型为 Int32 (拆箱)

System.DynamicIDynamicMetaObjectProviderDynamicMetaObjectStaticMemberDynamicWrapper

dynamic stringType = new StaticMemberDynamicWrapper(typeof(String));
var r = stringType.Concat("A", "B"); // 动态调用String的静态Concat方法

标签: C#

Powered by emlog  蜀ICP备18021003号-1   sitemap

川公网安备 51019002001593号