频 道 直 达 - 新闻 - 培训 - 软件 - 教程 - 前沿 - 组网 - 系统应用 - 安全 - 编程 - 存储 - 操作系统 - 数据库 - 服务器 - 专题 - 产品 - 案例库 - 读书 - 博客 - BBS
51CTO.COM_中国最大的网络技术网站
找资料:

6.5.2 多重数据类型转换

作者: Christian Nagel等著 李铭 译 黄静 审校 出处:清华大学出版社  2007-12-14 14:33    砖    好    评论   进入论坛
阅读提示:《C#2005&.NET_3.0高级编程》(第5版)第六章介绍的是基本语言元素和C#语言的扩展功能。本文主要介绍的是多重数据类型转换。

6.5.2  多重数据类型转换

在定义数据类型转换时必须考虑的一个问题是,如果在进行要求的数据类型转换时,C#编译器没有可用的直接转换方式,C#编译器就会寻找一种方式,把几种转换合并起来。例如,在Currency结构中,假定编译器遇到下面的代码:

Currency balance = new Currency(10,50);
long amount = (long)balance;
double amountD = balance;


首先初始化一个Currency实例,再把它转换为一个long。问题是不能定义这样的转换。但是,这段代码仍可以编译成功。因为编译器知道我们要定义一个从Currency到float的隐式转换,而且它知道如何显式地从float 转换为long。所以它会把这行代码编译为中间语言代码,首先把balance转换为float,再把结果转换为long。上述代码的最后一行也是这样,把balance转换为double型时,因为从Currency到 float的转换和从float 到double的转换都是隐式的,就可以在代码中把这个转换当作一种隐式转换。如果要显式地指定转换过程,可以编写如下代码:

Currency balance = new Currency(10,50);
long amount = (long)(float)balance;
double amountD = (double)(float)balance;

但是,在大多数情况下,这会使代码变得比较复杂,因此是不必要的。下面的代码会产生一个编译错误:

Currency balance = new Currency(10,50);
long amount = balance;

原因是编译器可以找到的最佳匹配的转换仍是首先转换为flost,再转换为long,但从float到long的转换需要显式指定。

所有这些都不会带来太多的麻烦。转换的规则是非常直观的,主要是为了防止在开发人员不知情的情况下丢失数据。但是,在定义数据类型转换时如果不小心,编译器就有可能指定一条导致不期望的结果的路径。例如,假定编写Currency结构的其他小组成员要把一个uint转换为Currency,而该uint中包含了美分的总数(美分不是美元,因为我们不希望丢掉美元的小数部分),为此应编写如下代码:

public static implicit operator Currency (uint value)
{
   return new Currency(value/100u, (ushort)(value%100));
} // Don't do this!

注意,在这段代码中,第一个100后面的u可以确保把value/100u解释为uint。如果写成value/100,编译器就会把它解释为一个int型的值,而不是uint型的值。

在这段代码中清楚地注释了“不要这么做”。下面说明其原因。看看下面的代码段,它把包含350的uint转换为一个Currency,再转换回uint。那么在执行完这段代码后,bal2中又将包含什么?

uint bal = 350;
Currency balance = bal;
uint bal2 = (uint)balance;

答案不是350,而是3!这是符合逻辑的。我们把350隐式地转换为Currency,得到balance.Dollars=3,balance.Cents=50。然后编译器进行通常的操作,为转换回uint指定最佳路径。balance最终会被隐式地转换为float型(其值为3.5),然后显式地转换为uint型,其值为3。

当然,转换为另一个数据类型后,再转换回来有时会丢失数据。例如,把包含6.8的float转换为int,再转换回float,会丢失数字中的小数部分,得到5,但丢失数字中的小数部分和一个整数被100整除的情况略有区别。Currency现在成了一种相当危险的类,它会对整数进行一些奇怪的操作。

问题是,在转换过程中如何解释整数是有矛盾的。从Currency到float的转换会把整数1解释为1美元,但从uint到Currency的转换会把这个整数解释为1美分,这是很糟糕的。如果希望类易于使用,就应确保所有的转换都按一种互相兼容的方式执行,即这些转换应得到相同的结果。在本例中,显然要重新编写从uint到Currency的转换,把整数值1解释为1美元:

public static implicit operator Currency (uint value)
{
   return new Currency(value, 0);
}

偶尔也会觉得这种新的转换方式可能根本不需要。但实际上这种转换方式是非常有用的。没有它,编译器在执行从uint到Currency的转换时,就只能通过float来进行。此时直接转换的效率要高得多,所以进行这种额外转换会提高性能,但需要确保它的结果与通过float进行转换得到的结果相同。在其他情况下,也可以为不同的预定义数据类型分别定义转换,让更多的转换隐式执行,而不是显式地执行,但本例不是这样。

测试这种转换是否成功,应确定无论使用什么转换路径,它都能得到相同的结果(而不是像在从float到int的转换过程中丢失数据那样)。Currency类就是一个很好的示例。下面的代码:

Currency balance = new Currency(50, 35);
ulong bal = (ulong) balance;

目前,编译器只能采用一种方式来执行这个转换:把Currency隐式地转换为float,再显式地转换为ulong。从float到ulong的转换需要显式指定,本例就显式指定了这个转换,所以编译是成功的。

但假定要添加另一个转换,从Currency隐式地转换为uint,就需要修改Currency结构,添加从uint到Currency的转换和从Currency到uint的转换,这段代码可以下载,作为SimpleCurrency2示例:

      public static implicit operator Currency (uint value)
      {
         return new Currency(value, 0);
      }

      public static implicit operator uint (Currency value)
      {
         return value.Dollars;
      }

现在,编译器从Currency转换到 ulong可以使用另一条路径:先从Currency隐式地转换为uint,再隐式地转换为ulong。该采用哪条路径? C#有一些规则(本书不详细讨论这些规则,读者可参阅MSDN文档说明),告诉编译器如何确定哪条是最佳路径。但最好自己设计转换,让所有的转换都得到相同的结果(但没有精确度的损失),此时编译器选择哪条路径就不重要了(在本例中,编译器会选择Currency→uint→ulong路径,而不是Currency→float→ulong路径)。

为了测试SimpleCurrency2示例,给SimpleCurrency的测试程序添加如下代码:

try
{
   Currency balance = new Currency(50,35);

   Console.WriteLine(balance);
   Console.WriteLine("balance is " + balance);
   Console.WriteLine("balance is (using ToString()) " + balance.ToString());

   uint balance3 = (uint) balance;
   Console.WriteLine("Converting to uint gives " + balance3);

运行这个示例,得到如下所示的结果:

SimpleCurrency2
50
balance is $50.35
balance is (using ToString()) $50.35
Converting to uint gives 50
After converting to float, = 50.35
After converting back to Currency, = $50.34
Now attempt to convert out of range value of–$100.00 to a Currency:
Exception occurred: Arithmetic operation resulted in an overflow.

这个结果显示了到uint的转换是成功的,但丢失了Currency的美分部分(小数部分),把负的float 转换为 Currency也产生了预料中的溢出异常,因为float到Currency的转换本身定义了一个checked环境。

但是,这个输出结果也说明了进行转换时最后一个要注意的潜在问题:结果的第一行没有正确显示结余,显示了50,而不是$50.35。在下面的代码中:

   Console.WriteLine(balance);
   Console.WriteLine("balance is " + balance);
   Console.WriteLine("balance is (using ToString()) " + balance.ToString());

只有最后两行把Currency正确显示为一个字符串。这是为什么?问题是在把转换和方法重载合并起来时,会出现另一个不希望的错误源。下面用倒序的方式解释这段代码。

第三行的Console.WriteLine()语句显式调用Currency.ToString()方法,以确保Currency显示为一个字符串。第二行代码没有这么做。字符串"balance is "传送给Console.WriteLine(),告诉编译器这个参数应解释为字符串,因此要隐式地调用Currency.ToString()方法。

但第一行的Console.WriteLine()方法只是把原来的Currency结构传送给Console.Write Line()。目前Console.WriteLine()有许多重载,但它们的参数都不是Currency结构。所以编译器会到处搜索,看看它能把Currency转换为什么,以与Console.WriteLine()的一个重载方法匹配。如上所示,Console.WriteLine()的一个重载方法可以快速而高效地显示uint,且其参数是一个uint。因此应把Currency隐式地转换为uint。

实际上,Console.WriteLine()有另一个重载方法,它的参数是一个double,结果是显示该double的值。如果仔细看看第一个SimpleCurrency示例的结果,就会发现该结果的第一行就是使用这个重载方法把Currency显示为一个double。在这个示例中,没有直接把Currency转换为uint,所以编译器选择Currency→float→double作为可用于Console.WriteLine()重载方法的首选转换方式。但在SimpleCurrency2中可以直接转换为uint,所以编译器会选择后者。

如果方法调用带有多个重载方法,并要给该方法传送参数,而该参数的数据类型不匹配任何重载方法,就可以迫使编译器确定使用哪些转换方式进行数据转换,决定使用哪个重载方法(并进行相应的数据转换)。当然,编译器总是按逻辑和严格的规则来工作,但结果可能并不是我们所期望的。如果可能会出问题,最好显式指定转换路径。

【责任编辑:董书 TEL:(010)68476606】

回书目   上一节   下一节
专题
C#实用基础教程
专题:ASP.NET 2.0基础开发指南
.NET移动与嵌入式技术专题
.NET Framework新手入门专题
VS.NET实用开发专题
我也说两句

匿名发表

(如果看不清请点击图片进行更换)


中 国 最 大 的 网 络 技 术 网 站 ·
技 术 成 就 梦 想
订阅技术快讯
电子杂志下载
名称:SQL Server数据库管理精品黄皮书
简介:书中文章经过精挑细选,便于用户能根据自己的实际工作和学习,快速在本书寻找到相关资料。内容涵盖了SQL Server的安装与升级、语句查询、数据备份和恢复、自动化任务、数据同步、数据字典、安全和预防、性能和优化、集群等各方面应用信息,以及DBA管理人员在数据库管理工作中
名称:2007路由技术大全
简介:《2007路由技术大全》由51CTO.com网站特别策划制作,该书包括路由器技术、路由器产品、路由器配置、安全设置、路由器故障处理、路由器密码恢复,以及广大网友在实践使用中的心得经验和技巧文章,内容注重实用性,适用于初学者入门,也适合多年从业者提高,是一本实践和理论完
名称:网络安全精品应用黄皮书
简介:《2007精品网络安全黄皮书》包括了9个大类24个小类, 800余篇文章,内容包含了熊猫烧香病毒、DDOS攻击、ARP病等热点问题的介绍及解决方案。从病毒查杀、防范、系统、数据等各方面的安全设置到黑客技术的了解、防范,涉及到了安全应用的全部领域, 由浅至深内容全面。
浏览器的战国时代
浏览器的战国时代
ARP攻击防范与解决方案
ARP攻击防范与解决方案
NAC安全访问控制
NAC安全访问控制
· NAC安全访问控制
· 网络布线测试仪器
· Windows Server 2008专..
· Windows远程桌面应用
· 网络故障排除宝典
· 运营商封堵ADSL共享 中..
· 解析35岁技术人的价值..
· 世纪枭雄比尔盖茨的王..
· 主流品牌防火墙配置
· ASP.NET开发教程
· 超级计算机TOP500专题
· Vista SP1对决XP SP3
· SQL Server 2008/2005..
· 程序员如何成长?
· C#技术开发指南
· 虚拟化技术还有点“虚”
ARP攻击防范与解决方案
ARP攻击防范与解决方案
SQL Server 2008/2005全解
SQL Server 2008/2005全解
SOA 面向服务架构
SOA 面向服务架构
· SOA 面向服务架构
· SQL Server 2008/2005..
· Apache技术专题
· 三层交换技术专题
· SQL Server入门到精通
· Windows远程桌面应用
· C#技术开发指南
· Apache技术专题
· Windows集群服务应用
· C#技术开发指南
· 国际文档格式标准开战
· 路由器设置与口令恢复
· Linux 集群技术专题
· PHP开发应用手册
· SOA 面向服务架构
· 企业数据恢复指南
ARP攻击防范与解决方案
ARP攻击防范与解决方案
SQL Server 2008/2005全解
SQL Server 2008/2005全解
SQL Server入门到精通
SQL Server入门到精通
· SQL Server入门到精通
· SQL Server 2008/2005..
· SOA 面向服务架构
· Apache技术专题
· C#技术开发指南
· 三层交换技术专题
· Apache技术专题
· C#技术开发指南
· Windows远程桌面应用
· 企业数据恢复指南
· Windows集群服务应用
· 路由器设置与口令恢复
· Linux 集群技术专题
· SOA 面向服务架构
· 了解统一威胁管理(UTM)..
· 反垃圾邮件技术应用