文章目录
- 17.1 什么是转换
- 17.2 隐式转换
- 17.3 显示转换和强制转换
- 17.4 转换的类型
- 17.5 数字的转换
- 17.5.1 隐式数字转换
- 17.5.2 溢出检测上下文
- 17.5.3 显示数字转换
- 17.6 引用转换
- 17.6.1 隐式引用转换
- 17.6.2 显式引用转换
- 17.6.3 有效显式引用转换
- 17.7 装箱转换
- 17.7.1 装箱是创建副本
- 17.7.2 装箱转换
- 17.8 拆箱转换
- 17.9 用户自定义转换
- 17.9.1 用户自定义转换的约束
- 17.9.2 用户自定义转换的示例
- 17.9.3 评估用户自定义转换
- 17.9.4 多步用户自定义转换的示例
- 17.10 is 运算符
- 17.11 as 运算符
17.1 什么是转换
- 转换是接受一个类型的值并将它用作另一个类型的等价值的过程。
- 转换后的值应和原值一样,但其类型为目标类型。
17.2 隐式转换
-
隐式转换不会丢失数据或精度,C# 会自动做这些转换。
-
从位数更少的源类型转换为位数更多的目标类型时,目标中多出来的位需要用 0 或 1 填充。
- 对于无符号类型,目标类型多出来的最高位都用 0 来进行填充,称为零扩展。
图17.1 无符号转换中的零扩展 - 对于有符号类型,额外的最高位用源表达式的符号位进行填充,称为符号扩展。
- C# 没有无符号扩展。
17.3 显示转换和强制转换
C# 不会提供会造成值丢失的自动转换。此时,需要使用强制转换表达式,这称为显示转换。
- 一对圆括号,里面是目标类型。
- 圆括号后面是源表达式。
使用强制转换表达式,需要承担可能引起的丢失数据的后果。
17.4 转换的类型
- 除了标准转换,还可以为自定义类型定义隐式转换和显示转换。
- 有一个预定义的转换类型,称为装箱,可以将任何值类型转换为:
- object 类型。
- System.ValueType 类型。
- 拆箱可以将一个装箱的值转换为原始类型。
17.5 数字的转换
数字类型可以相互转换,但有些转换必须是显式的。
17.5.1 隐式数字转换
- 箭头方向表示存在从源类型到目标类型的隐式转换。
- 若没有沿着箭头方向的路径,则该类型转换必须是显式的。
17.5.2 溢出检测上下文
使用 checked 运算符或 checked 语句检测类型转换是否溢出。
- 代码片段是否被检查称作溢出检测上下文。
- 如果指定一个表达式或一个代码为 checked,CLR 会在转换产生溢出时抛出 OverflowException 异常。
- 如果代码不是 checked,转换会继续,不论是否产生溢出。
- 默认的溢出检测上下文是不检查。
(1)checked 和 unchecked 运算符
- 在 unchecked 上下文中,会忽略溢出,结果值为 208。
- 在 checked 上下文中,抛出了 OverflowException 异常。
(2)checked 语句和 unchecked 语句
checked 和 unchecked 控制的是一块代码中的所有转换,而不是单个表达式。
17.5.3 显示数字转换
(1)整数类型到整数类型
如果转换会丢失数据:
- checked 时,则抛出 OverflowException 异常。
- unckecked 时,丢失的位不会发出警告。
(2)float 或 double 转到整数类型
如果转换会丢失数据:舍掉小数,截断为最接近的整数。
- checked 时,则抛出 OverflowException 异常。
- unckecked 时,C# 将不定义其值是什么。
(3)decimal 到整数类型
如果结果值不在目标类型的范围内,则 CLR 会抛出 OverflowException 异常。
(4)double 到 float
double 被舍入到最接近的 float 值:
- 值太小,则被设为 +0 或 -0。
- 值太大,则被设为 +∞ 或 -∞。
(5)float 或 double 到 decimal
- 值太小,则被设为 0。
- 值太大,则抛出 OverflowException 异常。
(6)decimal 到 float 或 double
总是会成功,但可能会损失精度。
17.6 引用转换
引用类型对象由内存中的两部分组成:引用和数据。
引用转换接受源引用并返回指向堆中同一位置的引用,但是将引用“标记”为其他类型。
- 对于 myVar1,引用对象看上去是类型 B 的对象(实际上就是)。
- 对于 myVar2,同样的对象看上去像类型 A。
17.6.1 隐式引用转换
- 所有引用类型可以被隐式转换为 object 类型。
- 任何接口可以隐式转换为其继承的接口。
- 类可以隐式转换为:
- 继承链中的任何类。
- 实现的任何接口。
- 委托可以隐式转换为 .NET BCL 类和接口。
- ArrayS 数组(元素类型为 Ts)可以隐式转换为:
- .NET BCL 类和接口。
- 另一个满足如下条件的数组 ArrayT(元素类型为 Tt):
- 数组维度和 ArrayS 相同。
- Ts 和 Tt 都是引用类型。
- Ts 能隐式转换为 Tt。
17.6.2 显式引用转换
显式引用转换是从普通类型到更精确类型的引用转换,包括:
- 从 object 到任何引用类型的转换。
- 从基类到派生类的转换。
例如,将基类 A 的引用转换到派生类 B,并赋值给变量 myVar2.
- 如果 myVar2 尝试访问 Field2,将会导致内存错误。
- 这种不正确的强制转换会在运行时抛出 InvalidCastException 异常,但不会导致编译错误。
17.6.3 有效显式引用转换
以下 3 种显示转换能够成功进行:
-
显示转换没有必要。
即,已经发生了隐式转换。
-
源引用为 null。
尽管这样的类型转换通常不安全,但 C# 还是允许的。
- 源引用指向的实际数据可以安全地进行隐式转换。
- myVar2 看上去指向类型 A 的数据,但实际上指向的是类型 B。
17.7 装箱转换
装箱是一种隐式转换,接受值类型,在堆上创建一个完整的引用类型对象并返回其引用。
常见的装箱场景是将值类型当做参数传递给方法,但参数类型时对象的数据类型,装箱过程具体如下:
- 在堆上创建 int 类型对象。
- 将 i 的值复制给 int 对象。
- 返回 int 对象的引用 oi。
17.7.1 装箱是创建副本
装箱返回的是值的引用类型副本。即,装箱之后,值有两份副本——原始值类型和引用类型副本,每一个都可以独立操作。
17.7.2 装箱转换
任何值类型 ValueTypeS 都可以被隐式转换为 object、System.ValueType 或 InferfaceT 类型(如果 ValueTypeS 实现了 InferfaceT)。
17.8 拆箱转换
- 拆箱是显式转换。
- 系统在把值拆箱为 ValueTypeT 时执行了如下步骤:
- 检测到要拆箱的对象实际是 ValueTypeT 的装箱值。
- 将对象的值复制到变量。
- 尝试将一个值拆箱为非原始类型时会抛出 InvalidCastException 异常。
17.9 用户自定义转换
可以为类和结构定义隐式和显式转换。
- 需要 public 和 static 修饰符。
- implicit 和 explicit 分别表示隐式和显式转换。
17.9.1 用户自定义转换的约束
- 只可以为类和结构定义。
- 不能重定义标准隐式或显式转换。
- 对于源类型 S 和目标类型 T:
- S 和 T 类型不能相同。
- S 和 T 不能有继承关系。
- S 和 T 不能同时为接口或 object。
- 转换运算符必须定义在 S 或 T 类型的内部。
- 不能同时声明隐式转换和显式转换。
17.9.2 用户自定义转换的示例
隐式转换示例:
若使用显式转换,则需要:
17.9.3 评估用户自定义转换
完整转换中最多可以有 3 个步骤:
- 预备标准转换。
- 用户自定义转换。
- 后续标准转换。
用户自定义转换只能有一次。
17.9.4 多步用户自定义转换的示例
17.10 is 运算符
is 运算符用于检查转换是否能成功。
以下 3 种转换适用于 is 运算符:
- 引用转换。
- 装箱转换。
- 拆箱转换。
is 运算符不能用于用户自定义转换。
17.11 as 运算符
as 运算符和强制转换运算符类似,但不会抛出异常,转换失败仅返回 null。
as 运算符只能用于引用转换和装箱转换。