背景:                 
[本书目录] [图书首页] [本书讨论区]  
链接地址:http://www.17xie.com/read-58493.html    注册17xie 一起来写书 实现您的出书梦想!

6.2  类型的安全性

第1章提到中间语言(IL)可以对其代码强制加上强类型安全性。强类型支持.NET提供的许多服务,包括安全性和语言的交互性。因为C#这种语言会编译为IL,所以C#也是强类型的。这说明数据类型并不总是可互换的。本节将介绍基本类型之间的转换。

注意:

C#还支持在不同引用类型之间的转换,允许指定自己创建的数据类型如何与其他类型进行相互转换。这些论题将在本章后面讨论。

泛型是C# 2.0中的一个新特性,它可以避免对一些常见的情形进行类型转换,泛型详见第9章。

6.2.1  类型转换

我们常常需要把数据从一种类型转换为另一种类型。考虑下面的代码:

byte value1 = 10;

byte value2 = 23;

byte total;

total = value1 + value2;

Console.WriteLine(total);

在编译这些代码时,会产生一个错误:

Cannot implicitly convert type 'int' to 'byte' (不能把int类型隐式地转换为byte类型)。

问题是,我们把两个byte型数据加在一起时,应返回int型结果,而不是另一个byte。这是因为byte包含的数据只能为8位,所以把两个byte型数据加在一起,很容易得到不能存储在byte变量中的值。如果要把结果存储在一个byte变量中,就必须把它转换回byte。C#有两种转换方式:隐式转换方式和显式转换方式。

1. 隐式转换方式

只要能保证值不会发生任何变化,类型转换就可以自动进行。这就是前面代码失败的原因:试图从int转换为byte,而潜在地丢失了3个字节的数据。编译器不会告诉我们该怎么做,除非我们明确告诉它这就是我们希望的!如果在long型变量中存储结果,而不是byte型变量中,就不会有问题了:

byte value1 = 10;

byte value2 = 23;

long total;               // this will compile fine

total = value1 + value2;

Console.WriteLine(total);

这是因为long类型变量包含的数据字节比byte类型多,所以数据没有丢失的危险。在这些情况下,编译器会很顺利地转换,我们也不需要显式提出要求。

表6-4介绍了C#支持的隐式类型转换。

表  6-4

源  类  型

目 的 类 型

sbyte

short、int、long、float、double、decimal

byte

short、ushort、int、uint、long、ulong、float、double、decimal

short

int、long、float、double、decimal

ushort

int、uint、long、ulong、float、double、decimal

int

long、float、double、decimal

uint

long、ulong、float、double、decimal

long、ulong

float、double、decimal

float

double

char

ushort、int、uint、long、ulong、float、double、decimal

注意,只能从较小的整数类型隐式地转换为较大的整数类型,不能从较大的整数类型隐式地转换为较小的整数类型。也可以在整数和浮点数之间转换,其规则略有不同,可以在相同大小的类型之间转换,例如int/uint转换为 float,long/ulong转换为double,也可以从long/ulong转换回float。这样做可能会丢失4个字节的数据,但这仅表示得到的float值比使用double得到的值精度低,编译器认为这是一种可以接受的错误,而其值的大小是不会受到影响的。无符号的变量可以转换为有符号的变量,只要无符号的变量值的大小在有符号的变量的范围之内即可。

在隐式转换值类型时,可空类型需要额外考虑:

可空类型隐式转换为其他可空类型,应遵循表6-4中非可空类型的转换规则。即int? 隐式转换为long?、float?、double?和decimal?。

非可空类型隐式转换为可空类型也遵循表6-4中的转换规则,即int隐式转换为long?、float?、double?和decimal?。

可空类型不能隐式转换为非可空类型,此时必须进行显式转换,如下一节所述。这是因为可空类型的值可以是null,但非可空类型不能表示这个值。

2. 显式转换方式

有许多场合不能隐式地转换类型,否则编译器会报告错误。下面是不能进行隐式转换的一些场合:

int转换为short—— 会丢失数据

int转换为uint—— 会丢失数据

uint转换为int—— 会丢失数据

float转换为int—— 会丢失小数点后面的所有数据

任何数字类型转换为char —— 会丢失数据

decimal转换为任何数字类型—— 因为decimal 类型的内部结构不同于整数和浮点数

int? 转换为int—— 可空类型的值可以是null

但是,可以使用cast显式执行这些转换。在把一种类型强制转换为另一种类型时,要迫使编译器进行转换。类型转换的一般语法如下:

long val = 30000;

int i = (int)val;   // A valid cast. The maximum int is 2147483647

这表示,把转换的目标类型名放在要转换的值之前的圆括号中。对于熟悉C的程序员来说,这是数据类型转换的典型语法。对于熟悉C++数据类型转换关键字(如static_cast)的程序员来说,这些关键字在C#中不存在,必须使用C风格的旧语法。

这种类型转换是一种比较危险的操作,即使在从long转换为int这样简单的转换过程中,如果原来long的值比int的最大值还大,就会出问题:

long val = 3000000000;

int i = (int)val;         // An invalid cast. The maximum int is 2147483647

在本例中,不会报告错误,也得不到期望的结果。如果运行上面的代码,结果存储在i中,则其值为:

–1294967296

最好假定显式数据转换不会给出希望的结果。如前所述,C#提供了一个checked运算符,使用它可以测试操作是否会产生算术溢出。使用这个运算符可以检查数据类型转换是否安全,如果不安全,就会让运行库抛出一个溢出异常:

long val = 3000000000;

int i = checked ((int)val);

记住,所有的显式数据类型转换都可能不安全,在应用程序中应包含这样的代码,处理可能失败的数据类型转换。第13章将使用try和 catch语句引入结构化异常处理。

使用数据类型转换可以把大多数数据从一种基本类型转换为另一种基本类型。例如:给price加上0.5,再把结果转换为int:

double price = 25.30;

int approximatePrice = (int)(price + 0.5);

这么做的代价是把价格四舍五入为最接近的美元数。但在这个转换过程中,小数点后面的所有数据都会丢失。因此,如果要使用这个修改过的价格进行更多的计算,最好不要使用这种转换;如果要输出全部计算完或部分计算完的近似值,且不希望用小数点后面的数据去麻烦用户,这种转换是很好的。

下面的例子说明了把一个无符号的整数转换为char型时,会发生的情况:

ushort c = 43;

char symbol = (char)c;

Console.WriteLine(symbol);

结果是ASCII数为43的字符,即+号。可以尝试数字类型之间的任何转换(包括char),这种转换是成功的,例如把decimal转换为char,或把char转换为decimal。

值类型之间的转换并不仅限于孤立的变量。还可以把类型为double的数组元素转换为类型为int的结构成员变量:

struct ItemDetails

{

   public string Description;

   public int ApproxPrice;

}

//...

double[] Prices = { 25.30, 26.20, 27.40, 30.00 };

ItemDetails id;

id.Description = "Whatever";

id.ApproxPrice = (int)(Prices[0] + 0.5);

要把一个可空类型转换为非可空类型,或转换为另一个可空类型,但可能会丢失数据,就必须使用显式转换。重要的是,在底层基本类型相同的元素之间进行转换时,就一定要使用显式转换,例如int?转换为int,或float?转换为float。这是因为可空类型的值可以是null,非可空类型不能表示这个值。只要可以在两个非可空类型之间进行显式转换,对应可空类型之间的显式转换就可以进行。但如果从可空类型转换为非可空类型,且变量的值是null,就会抛出InvalidOperationException。例如:

int? a = null;

int  b = (int)a;     // Will throw exception

使用显式的数据类型转换方式,并小心使用这种技术,就可以把简单值类型的任何实例转换为另一种类型。但在进行显式的类型转换时有一些限制,例如值类型,只能在数字、char类型和enum类型之间转换。不能直接把Boolean数据类型转换为其他类型,也不能把其他类型转换为Boolean数据类型。

如果需要在数字和字符串之间转换,.NET类库提供了一些方法。Object类有一个ToString()方法,该方法在所有的.NET预定义类型中都进行了重写,返回对象的字符串表示:

int i = 10;

string s = i.ToString();

同样,如果需要分析一个字符串,获得一个数字或Boolean值,就可以使用所有预定义值类型都支持的Parse方法:

string s = "100";

int i = int.Parse(s);

Console.WriteLine(i + 50);   // Add 50 to prove it is really an int

注意,如果不能转换字符串(例如要把字符串Hello转换为一个整数),Parse方法就会注册一个错误,抛出一个异常。第13章将介绍异常。

6.2.2  装箱和拆箱

第2章介绍了所有类型,包括简单的预定义类型,例如int和char,以及复杂类型,例如从Object类型中派生的类和结构。下面可以像处理对象那样处理字面值:

string s = 10.ToString();

但是,C#数据类型可以分为在堆栈上分配内存的值类型和在堆上分配内存的引用类型。如果int不过是堆栈上一个4字节的值,该如何在它上面调用方法?

C#的实现方式是通过一个有点魔术性的方式,即装箱(boxing)。装箱和拆箱(unboxing)可以把值类型转换为引用类型,或把引用类型转换为值类型。这已经在数据类型转换一节中介绍过了,即把值转换为object类型。装箱用于描述把一个值类型转换为引用类型。运行库会为堆上的对象创建一个临时的引用类型“box”。

该转换是隐式进行的,如上面的例子所述。还可以手工进行转换:

int myIntNumber = 20;

object myObject = myIntNumber;

拆箱用于描述相反的过程,即以前装箱的值类型转换回引用类型。这里使用术语“cast”,是因为这种数据类型转换是显式进行的。其语法类似于前面的显式类型转换:

int myIntNumber = 20;

object myObject = myIntNumber;     // Box the int

int mySecondNumber = (int)myobject;    // Unbox it back into an int

只能把以前装箱的变量再转换为值类型。当o不是装箱后的int型时,如果执行上面的代码,就会在运行期间抛出一个异常。

这里有一个警告。在拆箱时,必须非常小心,确保得到的值变量有足够的空间存储拆箱的值中的所有字节。例如,C#的int只有32位,所以把long值(64位)拆箱为int时,会产生一个InvalidCastException异常:

long myLongNumber = 333333423;

object myObject = (object)myLongNumber;

int myIntNumber = (int)myObject;


字数:5741    最后更新:7个月以前 [04-10 21:21]happyskynet 修改
本页编辑者:happyskynet  
[前一页]:第6章 运算符和类型强…  [后一页]:6.3 对象的相等比较
[在本页中加入书签] [收藏本书] [推荐本书]
  17xie论坛 > 本书讨论区 > 本页评论   (共0条)
发表评论

用户名称 匿名发表
评论内容
验证码

关于我们 | 版权声明 | 免责声明 | 诚聘英才 | 联系我们 | 合作伙伴 | 友情链接 | 广告合作 | 提交意见
Copyright © 2007 17xie.com 互联网协同写书平台 京ICP备08002671号