隐式和显示转换通常都非常简单:
int i = 5;
float f = i; // int32到float的隐式转换
Byte b = (Byte) i; // int32到byte的显示转换
如果一不留神,安全的显示数值转换会引发一个运行时异常:
Object i = 5;
float l = (float) i; // System.InvalidCastException: Specified cast is not valid.
ArrayList a = new ArrayList();
int b;
for (int i = 0; i < 10; i++) {
b = 10;
a.Add(b);
}
float f = (float) a[0]; // System.InvalidCastException: Specified cast is not valid.
为什么从 int 转换为 float 是可以的,但从 int ArrayList[i] 转换到 float 就不行了呢?
类型转换 vs 数值转换
引发InvalidCastException 的原因并不是因为从 int 到 float 的显式转换。
而是因为我们把一个 int 型的值拆箱成 float。
值类型和引用类型的一个重要区别就是它们分配的内存位置不同。
值类型被分配在堆栈空间,引用类型被分配在堆空间。
装箱的意思是把值类型转化为引用类型。
下面几个操作就会触发装箱:
- 将值类型赋值给引用类型字段
- 将值类型添加到引用类型集合中
- 将值类型作为参数传递给采用引用类型的方法
拆箱的意思是把引用类型转化为值类型。
下面的几个操作会触发拆箱:
- 从引用类型中提取值类型
- 从引用类型集合中提取值类型
- 把引用类型作为参数传递给采用值类型的方法
拆箱的约束:
在拆箱时,CLR规定了两个约束:
- 如果引用类型的值时null,会引发
NullReferenceException。
- 如果引用不指向装箱时期望的值类型,会引发
InvalidCastException。
拆箱到不同于原始装箱值类型的值类型是不允许的。
Object i = 5; // 将整型5装箱
float l = (float)i; // 拆箱成float型
ArrayList a = new ArrayList();
int b;
for (int i = 0; i < 10; i++) {
b = 10;
a.Add(b); // 将整型数b装箱到引用类型集合a中
}
float f = (float) a[0]; // 将整型数b拆箱成float型
现在我们知道了问题所在,那么怎么解决呢?
System.Convert Namespace
如果你看了.NET Reference Source关于 System.Int32 的代码,你会发现它实现了 IConvertible接口。
public struct Int32 :
IComparable,
IFormattable,
IConvertible,
IComparable<Int32>,
IEquatable<Int32>
像 System.Int32 这样实现了 IConvertible 接口的值类型,可以使用静态方法 System.Convert 来进行拆箱和显示转换 。
下面是 .NET Reference Source中的 System.Convert.ToSingle 的定义:
public static float ToSingle(object value) {
return value == null? 0: ((IConvertible)value).ToSingle(null);
}
如果我们传递一个装箱过的 int 类型给.ToSingle(object value),这个方法就会调用System.Int32
struct中定义的.ToSingle()方法:
/// <internalonly/>
float IConvertible.ToSingle(IFormatProvider provider) {
return Convert.ToSingle(m_value);
}
下面来看正确的例子:
Object o = 5;
float f = Convert.ToSingle(o);
ArrayList a = new ArrayList();
int b;
for (int i = 0; i < 10; i++) {
b = 10;
a.Add(b);
}
Convert.ToSingle(a[0]);
here有更多示例代码。
总结
当我们试图进行一次显示数值转换时,得到了一个运行时错误。
造成这个错误的真正原因是因为我们把一个值类型转换为引用类型,再把这个引用类型转换回值类型。通常这两种操作被称作装箱和拆箱。
拆箱的一个规则是只能拆成最初的值类型。否则,编译器就会报一个InvalidCastException的错误。
我们的解决方案是利用静态类 System.Convert。它提供了.ToXXX 方法,这个方法可以被实现了 IConvertible 接口的值类型调用。
希望这篇文章解决了你的问题,帮助你了解了CLR在屏幕后做了什么。