3

I am wondering in the F# code below how to fetch the type associated with each union case via reflection

type AccountCreatedArgs = {
    Owner: string
    AccountId: Guid
    CreatedAt: DateTimeOffset
    StartingBalance: decimal
}

type Transaction = {
    To: Guid
    From: Guid
    Description: string
    Time: DateTimeOffset
    Amount: decimal
}

type AccountEvents =
    | AccountCreated of AccountCreatedArgs
    | AccountCredited of Transaction
    | AccountDebited of Transaction

I tried using FSharpType.GetUnionCases(typeof<AccountEvents>) but UnionCaseInfo does not provide any information about the case type (only the declaring type aka AccountEvents so not really useful in my case) =/


The answer of glennsl really helped me https://stackoverflow.com/a/56351231/4636721

What I really found handy in my case was:

let getUnionCasesTypes<'T> =
    Reflection.FSharpType.GetUnionCases(typeof<'T>)
    |> Seq.map (fun x -> x.GetFields().[0].DeclaringType)
Natalie Perret
  • 8,013
  • 12
  • 66
  • 129
  • You've already recieved a valid answer, but perhaps you could still provide some reasoning as to why you want to do this? Combining F# and reflection is a path to be trodded with some caution. (But perfectly fine if you know what you're doing and why, naturally) – Guran May 29 '19 at 06:33
  • @Guran I am using Marten with F# and at some points I need to provide the event types to Marten. – Natalie Perret May 29 '19 at 06:58
  • I see. I would (probably) prefer an explicit function and endure the chores of updating that when my types change rather than the constant worry that my reflection is messed up, but that's your choice and a completely unrelated question. – Guran May 29 '19 at 07:08
  • @Guran you made a reasonable point and in production this will probably be the case, however, I was also just curious about how to fetch those types in case of discriminated unions. – Natalie Perret May 29 '19 at 08:01
  • Curiosity is a valid reason by itself . A middle road might be to use a type provider, or even writing a custom one. https://learn.microsoft.com/en-us/dotnet/fsharp/tutorials/type-providers/creating-a-type-provider – Guran May 29 '19 at 09:11
  • @Guran huh a type provider for that? Hm could you elaborate a bit? You picked my curiosity. Not really sure how this could apply here – Natalie Perret May 29 '19 at 09:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/194103/discussion-between-guran-and-ehouarn-perret). – Guran May 29 '19 at 10:33

1 Answers1

6

UnionCaseInfo has a GetFields method which returns an array of PropertyInfos which describe each field/argument of the union case. For example:

FSharpType.GetUnionCases(typeof<AccountEvents>)
    |> Array.map(fun c -> (c.Name, c.GetFields()))
    |> printfn "%A"

will print

[|("AccountCreated", [|AccountCreatedArgs Item|]);
  ("AccountCredited", [|Transaction Item|]);
  ("AccountDebited", [|Transaction Item|])|]

The name assigned to a single field union case is "Item", and if multiple is "Item1", "Item2" etc. The field type itself can be retrieved from the PropertyType property of PropertyInfo, so:

FSharpType.GetUnionCases(typeof<AccountEvents>)
    |> Array.map(fun c -> (c.Name, c.GetFields() |> Array.map(fun p -> p.PropertyType.Name)))
    |> printfn "%A"

will thus print

[|("AccountCreated", [|"AccountCreatedArgs"|]);
  ("AccountCredited", [|"Transaction"|]);
  ("AccountDebited", [|"Transaction"|])|]
glennsl
  • 28,186
  • 12
  • 57
  • 75