隐式转换
隐式转换是系统的默认转换方式,即不需要特别声明即可在所有情况下进行。在进行C#隐式转换时,编译器不需要检查就能进行安全的转换处理,C#的隐式转换一般不会导致信息丢失。例如,下面的代码从int类型隐式地转换成了long类型。
int mm=20;
long nn=mm;
C#的简单类型中,bool和string是没有隐式转换的。编译器可以隐式执行的数值转换类型如下表所示。
类型 | 可转换为 |
---|---|
byte | short,ushort,uint,int,ulong,long,float,double,decimal |
sbyte | short,int,long,float,double,decimal |
short | int,long,double,decimal |
ushort | uint,int,ulong,long,float,double,decimal |
int | long,float,double,decimal |
uint | ulong,long,float,double,decimal |
long | float,double,decimal |
ulong | float,double,decimal |
float | double |
char | ushort,uint,int,ulong,long,float,double,decimal |
上表的内容不需要强记,只需记住各类型的取值范围即可。因为对于任何类型A,只要其取值范围完全包含在类型B的取值范围内,就可以隐式地将类型A转换成类型B。
各类型的取值范围如下表所示:
类型 | 描述 | 范围 | 默认值 |
---|---|---|---|
bool | 布尔型 | True或False | False |
byte | 8位无符号整数 | 0~255 | 0 |
char | 16位Unicode字符 | U +0000~U +ffff | ‘\0’ |
decimal | 128位精确的十进制值,28-29有效位数 | (-7.9 x 1028 ~ 7.9 x 1028) / 100~28 | 0.0M |
double | 64位双精度浮点数 | (+/-)5.0 x 10-324 到 (+/-)1.7 x 10308 | 0.0D |
float | 32 位单精度浮点型 | -3.4 x 1038 到 + 3.4 x 1038 | 0.0F |
int | 32 位有符号整数类型 | -2,147,483,648 到 2,147,483,647 | 0 |
long | 64 位有符号整数类型 | -923,372,036,854,775,808 到 9,223,372,036,854,775,807 | 0L |
sbyte | 8 位有符号整数类型 | -128 到 127 | 0 |
short | 16 位有符号整数类型 | -32,768 到 32,767 | 0 |
uint | 32 位无符号整数类型 | 0 到 4,294,967,295 | 0 |
ulong | 64 位无符号整数类型 | 0 到 18,446,744,073,709,551,615 | 0 |
ushort | 16 位无符号整数类型 | 0 到 65,535 | 0 |
在进行隐式转换并在C#里对float变量赋值时,有一种特殊情况。看下面的代码:
float y = 3.5;
上述代码将产生一个编译器错误,因为类似3.5这样带有小数部分的数字常量会被C#认为是拥有更高精度的double值,因此编译器将因精度损失而拒绝执行。要进行这样的赋值,可以在小数后面加后缀F,强制编译器将赋值语句右面的常量看作浮点值:
float y = 3.5F;
也可以采用显示转换的方式:
float = (float) 3.5;
我们还可以选择采用double变量而不是float变量来表示浮点数,这样可以避免类型转换操作。
显式转换
显示转换是一种强制性的转换方式,在使用显式转换时,必须在代码中明确声明要转换的类型。
在C#中进行变量的显式转换时,需要注意下面两点。
- 隐式转换是显式转换的一种特例,所以将隐式转换书写成显式转换的格式是合法的。
- 显式转换并不安全,因为不同类型的变量取值范围是不同的,所以如果强制执行显式转换,可能会造成数据的丢失。
C#显式转换的语法格式如下:
类型 变量名 = (类型) 变量名
编译器可以显式执行的数值转换类型如下表所示。
类型 | 可转换为 |
---|---|
byte | sbyte,char |
sbyte | byte,ushort,uint,ulong,char |
short | sbyte,byte,ushort,uint,ulong,long,char |
ushort | sbyte,byte,short,char |
int | sbyte,byte,short,ushort,uint,ulong,char |
uint | sbyte,byte,short,ushort,int,char |
long | sbyte,byte,short,ushort,uint,int,ulong,char |
ulong | sbyte,byte,short,ushort,uint,int,long,char |
float | sbyte,byte,short,ushort,uint,int,ulong,long,char,float |
char | sbyte,byte,short |
decimal | sbyte,byte,short,ushort,uint,int,ulong,long,char,float,double |
double | sbyte,byte,short,ushort,uint,int,ulong,long,char,float,decimal |
我们可以将一个取值范围大的变量类型显式转换成取值范围小的变量类型,而不能采用隐式转换实现这种操作,例如:
double mm = 111;
long nn = (long)mm; //是正确的,可以将一个取值范围大的变量类型显式转换成取值范围小的变量类型
long nn = mm; //是错误的,无法将一个取值范围大的变量类型隐式转换成取值范围小的变量类型
装箱与拆箱
C#中的值类型和引用类型在本质上是同源的,所以不但可以将值类型转换成值类型,将引用类型转换成引用类型,也可以在值类型和引用类型之间进行转换。在C#中通常将值类型转换成引用类型的过程叫做装箱,将引用类型转换成值类型的过程叫做拆箱。
装箱与拆箱是整个C#类型系统的核心模块,在值类型和引用类型之间架起了一座沟通的桥梁。最终使任何类型值都可以转换为object类型值,object类型值也可以转换为任何类型的值,并把类型值作为一个对象来处理。
1. 装箱
装箱允许将值类型转换为引用类型,具体说明如下:
- 从任何的值类型到类型object的转换。
- 从任何的值类型到类型System.ValueType的转换。
- 从任何的值类型到类型的接口转换。
- 从任何的枚举类型到类型System.Enum的转换。
-
在C#中将一个值类型装箱为一个引用类型的流程如下:
- (1) 在托管堆中创建一个新的对象实例,并分配相应的内存。
- (2) 将值类型变量值复制到对象实例中。
- (3) 将对象实例地址压入堆栈中,并指明一个引用类型。
下面的代码演示了如何进行装箱操作。
namespace test
{
class Program
{
static void Main(string[] args)
{
int mm = 50;
object nn = mm;
Console.WriteLine("值为{0}, 装箱对象为{1}", mm, nn);
mm = 100;
Console.WriteLine("值为{0}, 装箱对象为{1}", mm, nn);
Console.ReadKey();
}
}
}
在上述代码中,首先定义了变量mm的数值类型为int,且初始值为50,然后将mm的值装箱处理为对象nn。为了测试,我们先将变量mm的值50进行装箱,转换为对象nn,再将变量mm的值修改为100,观察其对对象nn的影响。
上述代码的输出结果为
从结果可以看出,值类型的对象被复制到装箱得到的对象中,装箱后如果改变值类型的值,并不会影响到装箱对象的值。
2. 拆箱
拆箱允许将引用类型转换为值类型,具体说明如下:
- 从类型object到任何值类型的转换。
- 从类型System.ValueType到任何值类型的转换。
- 从任何的接口类型到对应的任何值类型的转换。
- 从类型System.Enum到任何枚举类型的转换。
-
在C#中将一个值类型拆箱为一个引用类型的流程如下:
- (1) 检查该对象实例是否是某个给定的值类型装箱之后的值。
- (2) 如果是则将值从实例中复制出来。
- (3) 赋值给值类型变量。
下面的实例可以说明C#拆箱操作的实现过程。
namespace test
{
class Program
{
static void Main(string[] args)
{
int mm = 50;
object nn = mm;
Console.WriteLine("装箱: 值为{0}, 装箱对象为{1}", mm, nn);
int zz = (int)nn;
Console.WriteLine("拆箱: 拆箱对象为{0}, 值为{1}", nn, zz);
Console.ReadKey();
}
}
}
上述代码实现了两个功能,一是将mm的值50进行装箱,转换为对象nn,二是将对象nn的值进行拆箱,转换为变量zz。
程序运行结果如下图。
在上述拆箱处理过程中,必须保证处理变量的类型一致,否则会出现异常。例如将上述实例中的拆箱处理代码进行如下修改:
double zz = (double)nn;
则程序只执行装箱结果,不执行拆箱结果,如下:
同时,编译器会提示异常编译信息: