9.4.2.2 F# 中的向上转换和向下转换(UPCASTSAND DOWNCASTS)
如果类型之间的转换不会失败,就称为向上转换(upcast)。我们已经看到,把类型转换成由该类型实现的接口,就是这种情况;另一个示例是把派生类转换成它的基类,在这种情况下,编译器也可以保证操作是正确的,不会失败。
如果有一个基本类型的值,希望将它转换为继承类,操作可能会失败,因为基类的值可能是目标类的值,也可能不是。在这种情况下,我们必须使用第二种类型转换,称为向下转换(downcast)。让我们用一个示例来说明,我们将使用标准的 Random 类,它是从 Object 类派生的(就像任何其他的 .NET 类一样):
> open System;;
> let rnd = new Random();;
val rnd : Random
> let rndObject = (rnd :>Object);; <-- 成功,操作不会失败
val obj : Object
> let rnd2 = (rndObject :>Random);; |
stdin(4,12): error: Type constraintmismatch. | 向上转换不正确
The type ‘Object‘ is not compatible withthe type ‘Random‘ |
> let rnd2 = (rndObject :?>Random);; <-- 成功,但可能会失败
val rnd2 : Random
> (rndObject :?> String);;
System.InvalidCastException: Unable to castobject of type
‘System.Random‘ to type ‘System.String‘.
可以发现,如果我们偶然尝试不适当地使用了向上转换,F# 编译器报告这个错误。错误消息说,Object 与 Random 不兼容,这就是说,编译器不能保证 Object 类型的值能够转换到 Random 类型。最后,清单显示,向下转换可能失败,并且,如果试着将对象转换到错误的继承类,会抛出异常。
要想记住 F# 向上转换(>)和向下转换(:?>)的语法,有一个好办法,在使用向下转换时,有一些不确定因素,因为操作可能会失败。这种不确定性是向下转换运算符包含问号,而向上转换不包含的原因。F# 语言还提供了相当于 C# 中 is 的运算符,返回布尔值,说明对象实例是否可以转换为指定的类型。为了测试 obj 是否可以转换为 String,写成 obj :?> String。
在这里,思考一下 F# 和 C# 之间的差异,是值得的。在 C# 中,我们甚至不需要类型转换,如在清单 9.17 中:当编译器知道转换能成功,它不需要消歧,可以让它隐式发生。F# 不进行任何隐式转换,因此,用一个语言结构来表示转换,保证成功,是有道理的。在 C# 中还要使用,就没有什么意义了,所以很少使用,因为两种转换使用相同的语法,更简单。明确指定转换的编程风格,使得类型推断成为可能,而且,通常有助于阐明代码实际上在做什么。
只用一章(合理的大小!)的篇幅,就想讲清所有F# 面向对象的特性,是不可能的,但我们已经知道要把函数式应用程序转变成实际的 .NET 代码,那些是最重要的。
我们已经多次说过,这些改变使 F# 代码更容易在 C# 中访问,因此,现在是证明的时候了,同时,要展示互操作是如何配合的。