18. 转换

来源:互联网 发布:linux 命令下载文件 sz 编辑:程序博客网 时间:2024/06/03 18:00

18.1 什么是转换

在赋值之前,源的值必须转换成目标类型的值。

  • 转换(conversion)是接受一个类型的值并使用它作为另一个类型的等价值的过程。
  • 转换后的值应和源值一样的,但它是目标类型。


18.2 隐式转换

有些类型的转换不会丢失数据或精度。例如,在不丢失数据的情况下,很容易把8位的值转换为16位的值。

  • 语言会自动做这些转换,这叫做隐式转换。
  • 从位数更少的源转换为位数更多的目标类型时,目标中多出来的位需要用0或1填充。
  • 当从更小的无符号类型转换为更大的无符号类型时,目标类型多出来的最高位都以0进行填充,这叫做零扩展。
对于有符号类型的转换而言,额外的高位用源表达式的符号位进行填充。
  • 这样就维持了被转换的值的正确符号和大小。
  • 这被叫做符号扩展。



18.3 显式转换和强制转换

如果要把短类型转换为长类型,对于长类型来说,保存所有短类型的字符很简单。然而,在其他情况下,目标类型也许不能在不损失数据的情况下提供源值。

例如,假设我们希望把ushort值转化为byte。

  • ushort可以保存任何0~65535之间的值。
  • byte只能保存0~255之间的值。
  • 只要希望转换的ushort值小于255,那么就不会损失数据。然而,如果更大,最高位的数据会丢失。
强制转换

对于预定义的类型,C#会自动将一个数据类型转换为另一个数据类型,但只是针对那么些源类型到目标类型不会发生数据丢失的情况。也就是说,对于源类型的任意值在被转换成目标类型时会丢失值的情况,语言是不会提供这两种类型的自动转换的。如果希望对这样的类型进行转换,就必须使用显式转换。这叫做强制转换表达式。

强制表达式,在源表达式前,加包含目标类型的一组配对圆括号。    (sbyte)var1;


如果我们使用强制转换表达式,这意味着我们要承担执行操作可能会引起的丢失数据的责任。从本质上是说:“不管是否会发生数据丢失,我知道在做什么,总之进行转换吧。”(然而,需要你确信你知道在做什么)




18.4 转换的类型

有很多标准的、预定义的用于数字和引用类型的转换。

  • 除了标准转换,还可以为自定义类型定义隐式转换和显式转换。
  • 还有一个预定义的转换类型,叫做装箱,它转换任何值类型到:
    • object类型
    • System.ValueType类型
  • 拆箱转换装箱的值到原始类型。



18.5 数字的转换

任何数字类型都可以转换成其他数字类型。一些转换是隐式的,而另外一些转换则应该是显式的。

隐式数字转换

  • 如果有路径,从源类型到目标类型可以按照箭头进行隐式转换。
  • 任何在从源类型到目标类型的箭头方向上没有路径的数字转换可能都是显式转换。





溢出检测上下文

我们已经知道了,显式转换可能会丢失数据并且不能在目标类型中同等地表示源值。对于整数类型,C#给我们提供了选择运行时是否应该在进行类型转换时检测结果溢出的能力。这将通过checked运算符和checked语句来实现。

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

checked和unchecked运算符控制表达式的溢出检测上下文。表达式放置在一对圆括号内并且不能是一个方法。语法如下所示:

checked(表达式)
unchecked(表达式)

checked语句和unchecked语句

checked和unchecked运算符用于圆括号内的单个表达式。checked和unchecked语句执行相同的功能,但控制一块代码的所有转换,而不是单个表达式。

checked语句和unchecked语句可以被嵌套在任意层次。


显式数字转换

隐式转换之所以能自动从源表达式转换到目标类型是因为不可能丢失数据。然而,对于显式转换而言,就可能丢失数据。因此,作为一个程序员,知道发生数据丢失时转换会如何处理很重要。

整数到整数

在checked情况下,如果转换会丢失数据,操作会抛出一个OverflowExceptoin异常。在unchecked情况下,丢失的位不会发出警告。(丢弃高位,仅保留低位)

float或double到整数

当把浮点类型转换为整数类型时,值会向零的方法取整也就是截断为最接近的整数。如果截断后的值不在目标类型的范围内:

  • 如果溢出检测上下文是checked,则CLR会抛出OverflowException异常。
  • 如果上下文是unchecked,则C#将不定义它的值应该是什么。

decimal到整数

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

double到float

float类型的值占32位,而double类型的值占64位。double类型的值被舍入到最接近的float类型的值。

  • 如果值太小而不能用float表示,那么值会被设置为正或负0。
  • 如果值太大而不能用float表示,那么值会被设置为正或负无穷大。

float或double到decimal

  • 如果值太小而不能用decimal表示,那么值会被设置为0。
  • 如果值太大,那么CLR会抛出OverflowException异常。
decimal到float或double

从decimal类型转换到float类型总是会成功。然而,可能会损失精度。



18.6 引用转换

至此,我们已经知道引用类型对象由内在中的两部分组成:引用和数据。

  • 由引用保存的那部分信息是它指向的数据类型。
  • 引用转换接受源引用并返回一个指向堆中同一位置的引用,但是把引用“标记”为其他类型。
隐式引用转换

就好像语言为我们自动实现的隐式数字转换一样,还有隐式引用转换。
  • 所有引用类型可以被隐式转换为object类型。
  • 任何类型可以隐式转换到它继承的接口。
  • 类可以隐式转换到:
    • 它继承的链中的任何类。
    • 它实现的任何接口。

显式引用转换

显式转换包括:

  • 从object类型到任何引用类型的转换。
  • 从基类到从它继承的类的转换。
如果转换的类型不受限制,很可能会导致我们尝试引用在内存中实际并不存在的类成员。然而,编译器确实允许这样的转换。到那时,如果系统在运行时遇到它们则会抛出一个异常。
运行时会捕获到不正确的强制转换并且招聘InvalidCastException异常。然而,注意,它不会导致编译错误。

有效显式引用转换

在运行时能成功进行(也就是不抛出InvalidCastException异常)的显式转换有三种情况。

  • 第一种情况:显式转换是没有必要的——也就是说,语言已经为我们进行了隐式转换。例如:从衍生类到基类的转换总是隐式转换的。
  • 第二种情况:源引用是null。
  • 第三种情况:由源引用指向的实际数据可以被安全地进行隐式转换。



18.7 装箱转换

包括值类型在内的所有C#类型都派生自object类型。然而,值类型是高效轻量的类型,因为默认情况下在堆上不包括它们的对象组件。然而,如果需要对象组件,我们可以使用装箱(boxing)。装箱是一种接受值类型的值,根据这个值在堆上创建一个完整的引用类型对象并返回对象引用的隐式转换。


装箱创建一份副本

一个有关装箱的普遍误解是在被装箱的项上发生了一些操作。其实不是,它返回了副本的值的引用类型。在装箱产生之后,有两份值——原始值类型和副本的引用类型,每一个都可以独立操作。

装箱转换

任何值类型ValueType都可以被隐式转换为object类型、SystemValueType或InferfaceT




18.8 拆箱转换

拆箱(unboxing)是把装箱后的对象转换回值类型的过程。

  • 拆箱是显式转换。
  • 系统在把值拆箱成ValueTypeT时执行了如下的步骤:
    • 它检测到拆箱的对象实际是ValueTypeT的装箱值。
    • 它把对象的值复制到变量。
尝试拆箱一个值为非原始类型时会抛出一个InvalidCastException异常。




18.9 用户自定义转换

除了标准转换,我们还可以为类和结构定义隐式和显式转换。

用户自定义转换的语法如下:

  • 除了implicit和explicit关键词之外,隐式和显式转换的声明语法是一样的。
  • 需要public和static修饰符。

public static implicit operator TargetType(SourceType Identifier)

{

return ObjectOfTargetType

例如,下面的代码给出了一个转换语法的示例,它转换一个Person类型的对象为int

public static implicit operator int(Person p)

{

return p.Age;

}


用户自定义转换的约束

用户自定义转换有一些很重要的约束,最重要的如下所示:

  • 只可以为类或结构定义用户自定义转换。
  • 不能重定义标准隐式转换或显式转换。
  • 对于源类型S和目标类型T,如下的命题是为真的:
    • S和T必须是不同类型。
    • S和T不能通过继承关联。也就是说,S不能继承自T,而T也不能从S继承。
    • S和T都不能是接口类型或object类型。
    • 转换运算符必须是S或T的成员。
  • 对于相同的源和目标类型,我们不能声明两个转换,一个是隐式转换而另一个是显式转换。

计算用户自定义转换

到目前为止讨论的用户自定义转换都是在单步内直接把源类型转换为目标类型对象。

但是,用户自定义转换也可以在完整转换中最多有3个步骤。它们包括:

  • 预备标准转换
  • 用户自定义转换
  • 后续标准转换
在这个链中不可能有一个以上的用户自定义转换。



18.10 is运算符

之前已经说过了,有些转换的请求是不成功的,并且会在运行时抛出一个InvalidCastException异常。我们可以使用is运算符来检查转换是否会成功完成,从而避免盲目尝试转换。

is运算符的语法如下,Expr是源表达式:

Expr is TargetType  //返回bool

如果Expr可能通过以下方式被成功转换为目标类型,运算符返回true:

  • 引用转换
  • 装箱转换
  • 拆箱转换
is运算符只可以用于引用转换以及装箱和拆箱转换,不能用于用户自定义转换。




18.11 as运算符

as运算符和强制转换运算符类似,只是它不抛出异常。如果转换失败,它把目标引用设置为null而不是抛出异常。

as运算符的语法如下,其中:

  • Expr是源表达式。
  • TargetType是目标类型,它必须是引用类型。

Expr as TargetType  //返回引用


由于as运算符返回引用表达式,它可以用作赋值中的源。

as运算符只能用于引用转换和装箱转换。它不能用于用户自定义转换或到值类型的转换。

0 0
原创粉丝点击