转换是接受一个类型的值并使用它作为另一个类型的 等价值 的过程。
1 转换的类型
转化类型可分为 预定义 和 用户自定义的。
其中预定义又可分为:
- 数字转换:其又可分为隐式或显式
- 装箱/拆箱:装箱用于将任何 值类型 转换为:
object
类型或System.ValueType
类型,拆箱用于还原 - 引用转换:多是基类与派生类的转化
用户自定义的就需要使用到 implicit
或 explicit
关键字了。
1.1 隐式转换
有些类型的转换不会丢失数据或精度,比如8位的数值转换为16位的数值,语言会自动做这些转换,这叫做 隐式转换。
- 零扩展:从更小的无符号类型转换为更大的无符号类型时,目标类型多出来的最高位都以 0 进行填充,这叫做 零扩展
- 符号扩展:对于有符号类型的转换而言,额外的高位用 源表达式的符号位 进行填充,这样叫做 符号扩展
1.2 显式转换和强制转换
如果有长类型转换为短类型,目标类型也许无法在不损失数据的情况下提供源值,很多情况下会导致数据溢出或丢失。此时还是想要继续转换,就必须使用 显式转换(强制转换表达式)。
示例:
(sbyte) varl1; // sbyte 是目标类型,var1 源表达式
1.2.1 溢出检测上下文
显式转化可能会丢失数据并且不能在目标类型中同等地表示源值,对于整数类型,可以通过 checked
运算符和 checked
语句来检测结果是否溢出。
代码片段是否被检测称作为 溢出检测上下文,默认的检测上下文是不检查。
checked
运算符和 unchecked
运算符:
ushort sh = 2000;
byte sb;
sb = unchecked((byte) sh); // 大多数重要的位丢失了
sb = checked((byte) sh); // 抛出overflowException异常
checked
语句和 unchecked
语句,它们与运算符的不同是,它们控制的是语句块:
byte sb;
ushort sh =2000;
checked
{
sb = (byte) sh;
}
1.2.1 显式数据的丢失状况
数值类型
类型 | 范围 | 大小 |
---|---|---|
sbyte | -128 到 127 | 有符号 8 位整数 |
byte | 0到255 | 无符号 8 位整数 |
char | U+0000 到 U+ffff | 16 位 Unicode 字符 |
short | -32,768 到 32,767 | 有符号 16 位整数 |
ushort | 0 到 65,535 | 无符号 16 位整数 |
int | -2,147,483,648 到 2,147,483,647 | 有符号 32 位整数 |
uint | 0 到 4,294,967,295 | 无符号 32 位整数 |
long | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | 有符号 64 位整数 |
ulong | 0 到 18,446,744,073,709,551,615 | 无符号 64 位整数 |
decimal | (-7.9 x 1028 到 7.9 x 1028) / 100 到 28 | 128 位精确的十进制值,28-29 有效位数 |
float | ±1.5e−45 到 ±3.4e38 | 32 位单精度 |
double | ±5.0e−324 到 ±1.7e308 | 64 位双精度 |
float
或double
转化为整数类型时,源值会舍掉小数,如果舍掉小数后的值仍不在目标类型的范围内,就根据 溢出检测上下文 来进行下一步操作。
2 引用转换
引用转换接受源引用并返回 一个指向堆中同一位置的引用,只是把引用标记为其他类型,而实际上二者都指向内存中的相同对象
2.1 隐式引用转换
与自动实现的隐式数值转换类似,还有隐式引用转换:
- 所有引用类型都可以被隐式转换为
object
类型 - 如何类型都可以隐式转换到他继承的接口
- 类可以隐式转换到他继承链中的任何类,以及它实现的任何接口
3 装箱和拆箱
包括值类型在类的所有C#类型都派生自
object
类型,只是 值类型 是高效轻量的类型,就是以object
类型作为桥梁,值类型与引用类型的相互转换
3.1 装箱
默认情况下,值类型在堆上不包括它们的对象组件,如果需要对象组件,可以使用装箱实现。
装箱是 隐式转换,它接受值类型的值,根据这个值在堆上创建一个完整的引用类型对象并返回其引用。其中原始值类型和返回的引用类型,每个都可以独立操作,互不干扰。
int i =12;
object oi = null;
oi = i;
3.2 拆箱
拆箱是把装箱后的对象转换回值类型的过程,拆箱是显式转换。
int i =10;
object oi = i;
int j = (int) oi; // 将一个值拆箱为非原始类型时会抛出InvalidCastException异常
4 用户自定义转换
用户自定义看 这里
在定义类和结构的时候,可以同时为其自定义转换。
定义时要点:
- 使用
implicit
或explicit
关键字,前者是隐式,后者是显式,二者后跟operator
运算符、目标类型 - 声明时需要
public
和static
修饰符 - 二者不能通过继承关联,二者必须是不同类型
- 二者都不能是
object
类型或接口类型
例如:
public static implicit operator int(Person p)
{
return p.Age;
}
5 is
运算符
有些转换是不成功的,就如同用于检测整数转换是否成功的 checked
运算符一样,is
运算符用于测定以下方式是否转换成功,成功返回true
:
- 引用转换
- 装箱/拆箱转换
语法:
Expr is TargetType; // 可以使用 if 语句判断是否为 true,再进行转换
6 as
运算符
as
运算符与强制转换运算符类似,只是它不抛出异常,如果失败,它返回的是 null
,所以赋值变量之前要先检查是否为 null
。
注意:as
运算符只能用于引用转换 和 装箱转换(没有拆箱转换)。
语法:
Expr as TargetType;