转换
- 转换
- 转换是接受一个类型的值并使用它作为另一个类型的等价值的过程
- 转换后的值应该和源值一样的,但其类型为目标类型
- 隐式转换
- 零扩充:目标多出来的最高为都以0填充
- 符号扩充:额外的高位用源表达式的符号位填充
- 显式转换和强制转换
- 转换的类型
- 转换
- 预定义的
- 数字
- 隐式
- 显式
- 引用
- 隐式
- 显式
- 拆箱/装箱
- 数字
- 用户自定义的
- 隐式
- 显式
- 预定义的
- 除了标准转换,还可以为自定义类型定义隐式转换和显式转换
- 还有一个预定义的转换类型,叫做装箱,可以将任意类型转换为
- object类型
- System.ValueType类型
- 拆箱可以将一个装箱的值转换为原始类型
- 还有一个预定义的转换类型,叫做装箱,可以将任意类型转换为
- 转换
- 数字的转换
- 隐式数字转换
- 如下关系存在隐式转换
- 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#提供了运行时检测结果移除的能力,通过checked和unchecked运算符实现
- 代码片段是否被检查称作溢出检测上下文
- 如果指定一个表达式或代码为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
- 总会成功,但是可能损失精度
- 隐式数字转换
- 引用转换
- 由引用保存的那部分信息是它执行的数据类型
- 引用转换接受源引用并返回一个指向堆中同一位置的引用,但是把引用标记为其他的类型
- 父类引用可以指向子类的对象
- 如果是父类引用指向子类对象,它看不到子类扩展父类的部分,即便是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直接存在隐式转换
- 以下.NET BCL类和接口
- 显式引用转换
- 显式转换包括
- 从object到任何引用类型
- 从基类型到继承它的类型
- 倒转上述隐式转换的方向,可以使用显式引用转换
- 编译器允许引用在内存中不存在的类型,但是运行到强制转换时会抛出InvalidCastException异常
- 有效的显式引用转换
- 显式转换没必要。
- 存在隐式转换时,显式转换是有效的,但是和隐式转换执行相同的内容
- 源引用是null。
- null可以转换成引用类型,在运行到转换时虽然不会抛出InvalidCastException异常
- 但是后面如果试图访问会抛出NullReferenceException异常
- 由源引用执行的实际数据可以被安全的进行隐式转换
- 如B是A的子类,一个A类引用实际指向B类对象,则把它转换为B类引用时是有效的
- 显式转换没必要。
- 显式转换包括
- 装箱转换
- 装箱概述
- 包括值类型在内的所有C#类型都派生自object类型
- 值类型是轻量高效的类型,默认情况下堆上不包括它们的对象组件
- 如果需要使用对象组件,可以使用装箱(boxing)
- 装箱是一种隐式转换,它接收值类型的值,根据这个值在堆上创建一个完整的引用类型的对象并返回引用
-
int i = 10; object oi = i;
- 装箱是创建副本
- 在装箱后该值有两份副本,原始值类型和引用类型副本,每一个都可以独立操作
- 装箱转换
- 任何值类型都可以被隐式转换为object类型、System.ValueType、InterfaveT(前提是实现了T接口)
- 装箱概述
- 拆箱转换
- 拆箱(unboxing)是把装箱后的对象转换回值类型的过程
- 拆箱是显式转换
- 系统把值拆箱成T类型时执行了如下步骤:
- 它检查到要拆箱的对象实际是T的装箱值
- 它把对象的值复制到变量
- 尝试将一个值拆箱为非原始类型时会抛出一个InvalidCastException异常
- 用户自定义转换
- 用户可以为类和结构定义显式和隐式转换
-
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
- 第一步为预备标准转换,第二步为用户自定义转换,第三步后续标准转换
- is运算符
- 源表达式 is 目标类型,返回bool
- 如果源表达式可以通过以下方式成功转换为目标类型,运算符返回true
- 引用转换
- 装箱转换
- 拆箱转换
- as运算符
- 类似于强转,只是不抛异常,转换失败会返回null
- 源表达式 as 目标类型,返回引用
- 由于as运算符返回引用表达式,它可以用作赋值表达式的源