I try to understand the concept of boxing and testing for base types, especially with tuples.
I have two objects from an external C# library that have different concrete types but share a common base type:
let o1 = ConcreteType1() // has base type BaseType
let o2 = ConcreteType2() // also has base type BaseType
If both o1
and o2
are derived from BaseType
, I have to perform some special comparison logic, so I'd like to test whether the elements of a tuple (o1, o2)
both have base type BaseType
.
Based on the answers to this question, I gather that I have to box each element of the type separately and perform the type tests on the individual elements, so that base types are considered:
match box o1, box o2 with
| (:? BaseType), (:? BaseType) -> // special logic with o1, o2
| _ -> // nope, some other behavior
My understanding is that simply boxing the tuple itself will not upcast the individual elements to obj
, and the test for their base types will therefore not work:
match box (o1, o2) with
| :? (BaseType * BaseType) -> // never hit, because elements are not upcast to obj
| _ -> // ...
Is this the correct explanation for the observed behavior, or are there other mechanisms involved?
It's true that boxing a tuple doesn't box the tuple's items. However, even if you manually boxed the items as well, it still wouldn't match. So the following code prints
"Unknown"
:This is because
ConcreteType1 * ConcreteType2
is not a subtype of eitherBaseType * BaseType
orobj * obj
. Note that the following code won't even compile:That means the following code will print
"ConcreteType"
:This is just the way that polymorphic types interact with OO subtyping. As another example,
List<ConcreteType1>
isn't a subtype ofList<BaseType>
, so no amount of boxing and casting will get them to match either.The confusing thing is that F# does have a special case where it will automatically cast a
ConcreteType1 * ConcreteType2
argument to match aBaseType * BaseType
parameter:I think this is because tupled arguments are common in other languages, so F# is trying to maintain compatibility, but I'm not certain.