一、基元类型
编译器直接支持的数据类型称为基元类型(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设置(项目属性->生成->高级->勾选检查算术溢出)
二、引用类型和值类型
引用类型总是从托管堆分配,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
由于未装箱值类型没有同步块索引,所以不能使用 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.Dynamic、 IDynamicMetaObjectProvider、 DynamicMetaObject、StaticMemberDynamicWrapper
dynamic stringType = new StaticMemberDynamicWrapper(typeof(String));
var r = stringType.Concat("A", "B"); // 动态调用String的静态Concat方法