转换

  1. 转换
    • 转换是接受一个类型的值并使用它作为另一个类型的等价值的过程
    • 转换后的值应该和源值一样的,但其类型为目标类型
  2. 隐式转换
    • 零扩充:目标多出来的最高为都以0填充
    • 符号扩充:额外的高位用源表达式的符号位填充
  3. 显式转换和强制转换
  4. 转换的类型
    • 转换
      • 预定义的
        • 数字
          • 隐式
          • 显式
        • 引用
          • 隐式
          • 显式
        • 拆箱/装箱
      • 用户自定义的
        • 隐式
        • 显式
    • 除了标准转换,还可以为自定义类型定义隐式转换和显式转换
      • 还有一个预定义的转换类型,叫做装箱,可以将任意类型转换为
        • object类型
        • System.ValueType类型
      • 拆箱可以将一个装箱的值转换为原始类型
  5. 数字的转换
    • 隐式数字转换
      • 如下关系存在隐式转换
        • byte:ushort、short
        • sbyte:short
        • char:ushort
        • ushort:uint、int
        • short:int
        • uint:ulong、long
        • int:long
        • ulong:float、decimal
        • long:float、decimal
        • float:double
        • double
        • decimal
      • 把上图看成有向图的邻接表,如果从A到B存在路径,则A可以隐式转换为B
    • 溢出检测上下文
      • C#提供了运行时检测结果移除的能力,通过checkedunchecked运算符实现
      • 代码片段是否被检查称作溢出检测上下文
        • 如果指定一个表达式或代码为checked,CLR会在转换产生溢出时抛出OverflowException异常
        • 如果代码不是checked,转换会继续而不管是否产生溢出
      • 默认的溢出检测上下文是不检查
      • checked和unchecked运算符
        • 表达式放在圆括号内并且不能是一个方法
          • checked(表达式)
          • unchecked(表达式)
        • 在unchecked上下文中会忽略溢出
        • 在checked上下文中如果发生溢出会抛出OverflowException异常
      • checked和unchecked语句
        • 运算符用于表达式,而语句执行相同的功能,但是控制的是一段代码中的所有转换
    • 显式数字转换
      • 对于显式类型转换可能会发生数据丢失,知道数据发生丢失时会如何处理很重要
      • checked情况下溢出会抛出异常,下面介绍的是unchecked或者无修饰的不检查的情况
      • S:源类型,T:目标类型
      • 整数类型到整数类型
        • S比T长?丢弃S中额外的最高位:符号扩展或零扩展S到T的长度
      • float或double到整数类型
        • 舍掉小数截断为最接近的整数
        • 如果截断后不在T的范围内,属于C#的未定义行为,可以使用checked修饰并处理异常
      • decimal到整数
        • 如果结果不在T的范围内,则抛出OverflowException异常
      • double到float
        • 被舍入到最接近的float值
        • 如果值太小不能用float表示,则设置为+0或-0
        • 如果值太大不能用float表示,则设置为+INF或-INF
      • float或double到decimal
        • 如果值太小不能使用decimal表示则设置为0
        • 如果值太大则会抛出溢出异常
      • decimal到float或double
        • 总会成功,但是可能损失精度
  6. 引用转换
    • 由引用保存的那部分信息是它执行的数据类型
    • 引用转换接受源引用并返回一个指向堆中同一位置的引用,但是把引用标记为其他的类型
    • 父类引用可以指向子类的对象
      • 如果是父类引用指向子类对象,它看不到子类扩展父类的部分,即便是public
      • 如果是子类重写的父类的方法,则用父类引用调用方法时调用的是子类重写的方法
      • 但是如果子类是使用new屏蔽的方法,父类调用时依然是调用父类本身的方法
    • 隐式引用转换
      • 任何引用类型可以隐式转换为object类型
      • 任何类型可以隐式转换到它继承的接口
      • 类可以隐式转换到
        • 它继承链中的任何类
        • 它实现的任何接口
      • 委托可以隐式转换成以下.NET BCL类和接口
        • System.Delegate、System.MulticastDelegate
        • System.ICloneable、System.Runtime.Serialization.ISerializable
      • ArrayS数组,其中元素是Ts类型,可以隐式转换成
        • 以下.NET BCL类和接口
          • System.Array
          • System.ICloneable
          • System.IList
          • System.ICollection
          • System.IEnumerable
        • 另一个数组ArrayT,其中元素类型Tt需要满足以下所有条件
          • 两个数组有一样的维度
          • 元素类型Ts和Tt都是引用类型
          • Ts和Tt直接存在隐式转换
    • 显式引用转换
      • 显式转换包括
        • 从object到任何引用类型
        • 从基类型到继承它的类型
      • 倒转上述隐式转换的方向,可以使用显式引用转换
      • 编译器允许引用在内存中不存在的类型,但是运行到强制转换时会抛出InvalidCastException异常
      • 有效的显式引用转换
        • 显式转换没必要。
          • 存在隐式转换时,显式转换是有效的,但是和隐式转换执行相同的内容
        • 源引用是null。
          • null可以转换成引用类型,在运行到转换时虽然不会抛出InvalidCastException异常
          • 但是后面如果试图访问会抛出NullReferenceException异常
        • 由源引用执行的实际数据可以被安全的进行隐式转换
          • 如B是A的子类,一个A类引用实际指向B类对象,则把它转换为B类引用时是有效的
  7. 装箱转换
    • 装箱概述
      • 包括值类型在内的所有C#类型都派生自object类型
      • 值类型是轻量高效的类型,默认情况下堆上不包括它们的对象组件
      • 如果需要使用对象组件,可以使用装箱boxing
      • 装箱是一种隐式转换,它接收值类型的值,根据这个值在堆上创建一个完整的引用类型的对象并返回引用
      • int i = 10;
        object oi = i;
        
    • 装箱是创建副本
      • 在装箱后该值有两份副本,原始值类型和引用类型副本,每一个都可以独立操作
    • 装箱转换
      • 任何值类型都可以被隐式转换为object类型、System.ValueType、InterfaveT(前提是实现了T接口)
  8. 拆箱转换
    • 拆箱unboxing)是把装箱后的对象转换回值类型的过程
    • 拆箱是显式转换
    • 系统把值拆箱成T类型时执行了如下步骤:
      • 它检查到要拆箱的对象实际是T的装箱值
      • 它把对象的值复制到变量
    • 尝试将一个值拆箱为非原始类型时会抛出一个InvalidCastException异常
  9. 用户自定义转换
    • 用户可以为类和结构定义显式和隐式转换
    • public static implicit/explicit operator TargetType (SourceType Identifier) {
          // ... 
          return ObjectOfTargetType;
      }
      
    • implicit是隐式转换,explicit是显式转换
    • 用户自定义转换的约束
      • 只可以为类或结构定义用户自定义转换
      • 不能重定义标准隐式转换或显式转换
      • 对于S和T如下命题为真
        • S和T是不同类型
        • S和T不能通过继承关联,也就是S不能继承自T,T也不能继承自S
        • S和T都不能是接口类型或者object类型
        • 转换运算符必须是S或T的成员
    • 评估用户自定义转换
      • 目前讨论的自定义转换都是在单步内直接把源类型转换为目标类型
      • 但是用户自定义转换在完整转换最多可以有3个步骤
        • 预备标准转换
        • 用户自定义转换
        • 后续标准转换
      • 这个链中不可能有一个以上的用户自定义类型转换
    • 多步用户自定义转换的示例
      • Employee继承自Person,所以从Employee到Person存在标准转换
      • Person中包含一个int类型的age字段,从int到float有标准转换
      • Person中定义了从Person到int类中的隐式转换,返回age
      • Employee => Person => int => float
      • 第一步为预备标准转换,第二步为用户自定义转换,第三步后续标准转换
  10. is运算符
    • 源表达式 is 目标类型,返回bool
    • 如果源表达式可以通过以下方式成功转换为目标类型,运算符返回true
      • 引用转换
      • 装箱转换
      • 拆箱转换
  11. as运算符
    • 类似于强转,只是不抛异常,转换失败会返回null
    • 源表达式 as 目标类型,返回引用
    • 由于as运算符返回引用表达式,它可以用作赋值表达式的源