Martin Thompson
Martin Thompson

Reputation: 3745

Casting a system.Object to a specific type in F#

I have a standard type that I use to pass messages and objects between functions that has an optional MessageObject that is System.Object.

type StatusMessage = {
    Message: string
    MessageObject: Object option
    Success: bool
}

In the following specific function - I know that the Message Object will always contain the following specific type and I want to be able to access that within the function

type SingleCorrectRecord = {
    CardTransactionWithOrder: CardWithOrder
    Line: RowTransaction
    ExchangeVariance: decimal
}

My function is:

let getEstimatedVariance(matchedTransactions: StatusMessage list): decimal = 
    let exchangeVariance:decimal = 
        matchedTransactions |> Seq.sumBy(fun mt -> mt.MessageObject.ExchangeVariance)
    estimatedVariance

Obviously mt.MessageObject doesn't contain the property "ExchangeVariance" but I need to be able to cast (or unbox?) the object so that it knows that it is a SingleCorrectRecord type and I can access the properties.

Any help is appreciated.

Upvotes: 3

Views: 305

Answers (2)

tranquillity
tranquillity

Reputation: 1685

:?> is the downcast operator (or :? within pattern matching) which can be used for this problem. However, it is typically not recommended to use downcasting, which can fail at runtime, in F# code.

As an alternative, you could structure your StatusMessage type to be generic or to use a discriminated union depending on whether messages with different payload types need to be stored in the same collection.

// This type can store any type of message however StatusMessage<SingleCorrectRecord> is 
// a different type to StatusMessage<Other> and they can't be stored in the same collection. 
type StatusMessage<'T> = {
    Message: string
    MessageObject: 'T option
    Success: bool
}

let getEstimatedVariance(matchedTransactions: StatusMessage<SingleCorrectRecord> list): decimal = 
    let estimatedVariance:decimal = 
        matchedTransactions |> Seq.sumBy(fun mt -> 
            match mt.MessageObject with
            | Some msg -> msg.ExchangeVariance
            | None -> 0M )
    estimatedVariance

If the messages need to be in a grouped collection you could define all messages in a MsgPayload discriminated union:

type MsgPayload = 
| SingleCorrectRecord of SingleCorrectRecord
| Other of string

type StatusMessage2 = {
    Message: string
    MessageObject: MsgPayload option
    Success: bool
}

let getEstimatedVariance2(matchedTransactions: StatusMessage2 list): decimal = 
    let estimatedVariance:decimal = 
        matchedTransactions |> Seq.sumBy(fun mt -> 
            match mt.MessageObject with
            | Some (SingleCorrectRecord msg) -> msg.ExchangeVariance
            | _ -> 0M )
    estimatedVariance

Upvotes: 4

kaefer
kaefer

Reputation: 5741

You can nest the dynamic type test with the union case pattern in the same match expression:

let exchangeVariance = 
    matchedTransactions |> Seq.sumBy (fun mt -> 
        match mt.MessageObject with
        | Some(:? SingleCorrectRecord as scr) -> scr.ExchangeVariance
        | _ -> 0M )
// val exchangeVariance : decimal

Upvotes: 4

Related Questions