Conversion between types in an object hierarchy is fundamental to object-oriented programming. There are two basic types of conversions: casting up (upcasting) and casting down (downcasting). Casting up a hierarchy means casting from a derived object reference to a base object reference. Such a cast is guaranteed to work as long as the base class is in the inheritance hierarchy of the derived class. Casting down a hierarchy, from a base object reference to a derived object reference, succeeds only if the object actually is an instance of the correct destination (derived) type or a type derived from the destination type.
F# provides operators for these types of conversions. The :> operator casts up the hierarchy, and the :?> operator casts down the hierarchy.
Upcasting
In many object-oriented languages, upcasting is implicit; in F#, the rules are slightly different. Upcasting is applied automatically when you pass arguments to methods on an object type. However, for let-bound functions in a module, upcasting is not automatic, unless the parameter type is declared as a flexible type. For more information, see Flexible Types (F#).
The :> operator performs a static cast, which means that the success of the cast is determined at compile time. If a cast that uses :> compiles successfully, it is a valid cast and has no chance of failure at run time.
You can also use the upcast operator to perform such a conversion. The following expression specifies a conversion up the hierarchy.
upcast expression
When you use the upcast operator, the compiler attempts to infer the type you are converting to from the context. If the compiler is unable to determine the target type, the compiler reports an error.
Downcasting
The :?> operator performs a dynamic cast, which means that the success of the cast is determined at run time. A cast that uses the :?> operator is not checked at compile time; but at run time, an attempt is made to cast to the specified type. If the object is compatible with the target type, the cast succeeds. If the object is not compatible with the target type, the runtime raises an InvalidCastException.
You can also use the downcast operator to perform a dynamic type conversion. The following expression specifies a conversion down the hierarchy to a type that is inferred from program context.
downcast expression
As for the upcast operator, if the compiler cannot infer a specific target type from the context, it reports an error.
The following code illustrates the use of the :> and :?> operators. The code illustrates that the :?> operator is best used when you know that conversion will succeed, because it throws InvalidCastException if the conversion fails. If you do not know that a conversion will succeed, a type test that uses a match expression is better because it avoids the overhead of generating an exception.
type Base1() =
abstract member F : unit -> unit
default u.F() =
printfn "F Base1"
type Derived1() =
inherit Base1()
override u.F() =
printfn "F Derived1"
let d1 : Derived1 = Derived1()
// Upcast to Base1.
let base1 = d1 :> Base1
// This might throw an exception, unless
// you are sure that base1 is really a Derived1 object, as
// is the case here.
let derived1 = base1 :?> Derived1
// If you cannot be sure that b1 is a Derived1 object,
// use a type test, as follows:
let downcastBase1 (b1 : Base1) =
match b1 with
| :? Derived1 as derived1 -> derived1.F()
| _ -> ()
downcastBase1 base1
Because generic operators downcast and upcast rely on type inference to determine the argument and return type, in the above code, you can replace
let base1 = d1: :> Base1
with
base1 = upcast d1
In the previous code, the argument type and return types are Derived1 and Base1, respectively.
For more information about type tests, see Match Expressions (F#).