C# 图解教程 第5版 —— 第17章 转换

2023-12-14 22:32:22

17.1 什么是转换

  • 转换是接受一个类型的值并将它用作另一个类型的等价值的过程。
  • 转换后的值应和原值一样,但其类型为目标类型。

17.2 隐式转换

  1. 隐式转换不会丢失数据或精度,C# 会自动做这些转换。

  2. 从位数更少的源类型转换为位数更多的目标类型时,目标中多出来的位需要用 0 或 1 填充。

    • 对于无符号类型,目标类型多出来的最高位都用 0 来进行填充,称为零扩展
    image-20231206215536631
    图17.1 无符号转换中的零扩展
    • 对于有符号类型,额外的最高位用源表达式的符号位进行填充,称为符号扩展
image-20231206215407890
图17.2 有符号转换中的符号扩展
  1. C# 没有无符号扩展。

17.3 显示转换和强制转换

? C# 不会提供会造成值丢失的自动转换。此时,需要使用强制转换表达式,这称为显示转换

  • 一对圆括号,里面是目标类型。
  • 圆括号后面是源表达式。
image-20231206215923366

? 使用强制转换表达式,需要承担可能引起的丢失数据的后果。

image-20231206220011328
图17.3 强制转换 ushort 为 byte

17.4 转换的类型

  • 除了标准转换,还可以为自定义类型定义隐式转换和显示转换。
  • 有一个预定义的转换类型,称为装箱,可以将任何值类型转换为:
    • object 类型。
    • System.ValueType 类型。
  • 拆箱可以将一个装箱的值转换为原始类型。
image-20231206220245548
图17.4 转换的类型

17.5 数字的转换

? 数字类型可以相互转换,但有些转换必须是显式的。

image-20231207000033891
图17.5 数字转换

17.5.1 隐式数字转换

  • 箭头方向表示存在从源类型到目标类型的隐式转换。
  • 若没有沿着箭头方向的路径,则该类型转换必须是显式的。
image-20231207000346245
图17.6 隐式数字转换

17.5.2 溢出检测上下文

? 使用 checked 运算符或 checked 语句检测类型转换是否溢出。

  • 代码片段是否被检查称作溢出检测上下文
    • 如果指定一个表达式或一个代码为 checked,CLR 会在转换产生溢出时抛出 OverflowException 异常。
    • 如果代码不是 checked,转换会继续,不论是否产生溢出。
  • 默认的溢出检测上下文是不检查。

(1)checked 和 unchecked 运算符

image-20231207000723523
  • 在 unchecked 上下文中,会忽略溢出,结果值为 208。
  • 在 checked 上下文中,抛出了 OverflowException 异常。
image-20231207000756617 image-20231207000856758

(2)checked 语句和 unchecked 语句

? checked 和 unchecked 控制的是一块代码中的所有转换,而不是单个表达式。

image-20231207001003795

17.5.3 显示数字转换

image-20231207001029226
图17.7 显式数字转换

(1)整数类型到整数类型

? 如果转换会丢失数据:

  • checked 时,则抛出 OverflowException 异常。
  • unckecked 时,丢失的位不会发出警告。
image-20231207001157207
图17.8 整数到整数的显示转换

(2)float 或 double 转到整数类型

? 如果转换会丢失数据:舍掉小数,截断为最接近的整数。

  • checked 时,则抛出 OverflowException 异常。
  • unckecked 时,C# 将不定义其值是什么。
image-20231207001531466
图17.9 转换 float 或 double 为整数类型

(3)decimal 到整数类型

? 如果结果值不在目标类型的范围内,则 CLR 会抛出 OverflowException 异常。

image-20231207001117587
图17.10 转换 decimal 到整数

(4)double 到 float

? double 被舍入到最接近的 float 值:

  • 值太小,则被设为 +0 或 -0。
  • 值太大,则被设为 +∞ 或 -∞。
image-20231207001853450
图17.11 转换 double 到 float

(5)float 或 double 到 decimal

  • 值太小,则被设为 0。
  • 值太大,则抛出 OverflowException 异常。
image-20231207001955651
图17.12 转换 float 或 double 到 decimal

(6)decimal 到 float 或 double

? 总是会成功,但可能会损失精度。

image-20231207002055355
图17.13 转换 decimal 到 float 或 double

17.6 引用转换

? 引用类型对象由内存中的两部分组成:引用和数据。

? 引用转换接受源引用并返回指向堆中同一位置的引用,但是将引用“标记”为其他类型。

  • 对于 myVar1,引用对象看上去是类型 B 的对象(实际上就是)。
  • 对于 myVar2,同样的对象看上去像类型 A。
image-20231207130723138 image-20231207130742716
图17.14 引用转换返回与对象关联的不同类型

17.6.1 隐式引用转换

  • 所有引用类型可以被隐式转换为 object 类型。
  • 任何接口可以隐式转换为其继承的接口。
  • 类可以隐式转换为:
    • 继承链中的任何类。
    • 实现的任何接口。
image-20231207130958291
图17.15 类和接口的隐式转换
  • 委托可以隐式转换为 .NET BCL 类和接口。
  • ArrayS 数组(元素类型为 Ts)可以隐式转换为:
    • .NET BCL 类和接口。
    • 另一个满足如下条件的数组 ArrayT(元素类型为 Tt):
      1. 数组维度和 ArrayS 相同。
      2. Ts 和 Tt 都是引用类型。
      3. Ts 能隐式转换为 Tt。
image-20231207131257551
图17.16 委托和数组的隐式转换

17.6.2 显式引用转换

? 显式引用转换是从普通类型到更精确类型的引用转换,包括:

  1. 从 object 到任何引用类型的转换。
  2. 从基类到派生类的转换。

? 例如,将基类 A 的引用转换到派生类 B,并赋值给变量 myVar2.

  • 如果 myVar2 尝试访问 Field2,将会导致内存错误。
  • 这种不正确的强制转换会在运行时抛出 InvalidCastException 异常,但不会导致编译错误。
image-20231207131740441
图17.17 无效的转换抛出运行时异常

17.6.3 有效显式引用转换

? 以下 3 种显示转换能够成功进行:

  1. 显示转换没有必要。

    即,已经发生了隐式转换。

image-20231207131957636
  1. 源引用为 null。

    尽管这样的类型转换通常不安全,但 C# 还是允许的。

image-20231207132043049
  1. 源引用指向的实际数据可以安全地进行隐式转换。
    • myVar2 看上去指向类型 A 的数据,但实际上指向的是类型 B。
image-20231207132224429
图17.18 强制转换到安全类型

17.7 装箱转换

? 装箱是一种隐式转换,接受值类型,在堆上创建一个完整的引用类型对象并返回其引用。

? 常见的装箱场景是将值类型当做参数传递给方法,但参数类型时对象的数据类型,装箱过程具体如下:

  • 在堆上创建 int 类型对象。
  • 将 i 的值复制给 int 对象。
  • 返回 int 对象的引用 oi。
image-20231207132702276
图17.19 装箱从值类型穿件了完整的引用类型

17.7.1 装箱是创建副本

? 装箱返回的是值的引用类型副本。即,装箱之后,值有两份副本——原始值类型和引用类型副本,每一个都可以独立操作。

image-20231207132927683 image-20231207133001587 image-20231207133021488
图17.20 装箱创建了一份可以被独立操作的副本

17.7.2 装箱转换

? 任何值类型 ValueTypeS 都可以被隐式转换为 object、System.ValueType 或 InferfaceT 类型(如果 ValueTypeS 实现了 InferfaceT)。

image-20231207133212039
图17.21 装箱是值类型到引用类型的隐式转换

17.8 拆箱转换

  • 拆箱是显式转换。
  • 系统在把值拆箱为 ValueTypeT 时执行了如下步骤:
    1. 检测到要拆箱的对象实际是 ValueTypeT 的装箱值。
    2. 将对象的值复制到变量。
  • 尝试将一个值拆箱为非原始类型时会抛出 InvalidCastException 异常。
image-20231207133524136 image-20231207133604848 image-20231207133618889 image-20231207133635312
图17.22 拆箱转换

17.9 用户自定义转换

? 可以为类和结构定义隐式和显式转换。

  • 需要 public 和 static 修饰符。
  • implicit 和 explicit 分别表示隐式和显式转换。
image-20231207133847913

17.9.1 用户自定义转换的约束

  • 只可以为类和结构定义。
  • 不能重定义标准隐式或显式转换。
  • 对于源类型 S 和目标类型 T:
    • S 和 T 类型不能相同。
    • S 和 T 不能有继承关系。
    • S 和 T 不能同时为接口或 object。
    • 转换运算符必须定义在 S 或 T 类型的内部。
    • 不能同时声明隐式转换和显式转换。

17.9.2 用户自定义转换的示例

? 隐式转换示例:

image-20231207134152236 image-20231207134244737 image-20231207134259771

? 若使用显式转换,则需要:

image-20231207134344549

17.9.3 评估用户自定义转换

image-20231207134420162
图17.23 单步用户自定义转换

? 完整转换中最多可以有 3 个步骤:

  1. 预备标准转换。
  2. 用户自定义转换。
  3. 后续标准转换。

? 用户自定义转换只能有一次。

image-20231207134539358
图17.24 多步用户自定义转换

17.9.4 多步用户自定义转换的示例

image-20231207134652719 image-20231207134730222 image-20231207134746499
图17.25 从 Employee 转换到 float

17.10 is 运算符

? is 运算符用于检查转换是否能成功。

image-20231207134912633

? 以下 3 种转换适用于 is 运算符:

  1. 引用转换。
  2. 装箱转换。
  3. 拆箱转换。

?

? is 运算符不能用于用户自定义转换。

17.11 as 运算符

? as 运算符和强制转换运算符类似,但不会抛出异常,转换失败仅返回 null。

image-20231207135137893

? as 运算符只能用于引用转换和装箱转换。

文章来源:https://blog.csdn.net/zheliku/article/details/134853815
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。