I want to get the equivalent of Enum.GetName
for an F# discriminated union member. Calling ToString()
gives me TypeName+MemberName, which isn't exactly what I want. I could substring it, of course, but is it safe? Or perhaps there's a better way?

- 117,631
- 17
- 236
- 300

- 23,067
- 22
- 97
- 166
-
1Beware! The very same expression, x.ToString(), will at different program runs sometimes give me AssemblyName+TypeName and sometimes AssemblyName+TypeName+MemberName. Another identical expression on the same type elsewhere will always give me AssemblyName+TypeName+MemberName. Same problem with x.GetType().Name. The accepted answer is good. – Bent Tranberg May 23 '16 at 08:28
4 Answers
You need to use the classes in the Microsoft.FSharp.Reflection
namespace so:
open Microsoft.FSharp.Reflection
///Returns the case name of the object with union type 'ty.
let GetUnionCaseName (x:'a) =
match FSharpValue.GetUnionFields(x, typeof<'a>) with
| case, _ -> case.Name
///Returns the case names of union type 'ty.
let GetUnionCaseNames <'ty> () =
FSharpType.GetUnionCases(typeof<'ty>) |> Array.map (fun info -> info.Name)
// Example
type Beverage =
| Coffee
| Tea
let t = Tea
> val t : Beverage = Tea
GetUnionCaseName(t)
> val it : string = "Tea"
GetUnionCaseNames<Beverage>()
> val it : string array = [|"Coffee"; "Tea"|]

- 1,493
- 1
- 16
- 21
-
1I came here for the ```GetUnionFields``` syntax - thanks for that. While I'm here I thought I'd point out that ```GetUnionCaseName``` could be written slightly more succinctly as: ```let GetUnionCaseName (e:'a) = ( FSharpValue.GetUnionFields(e, typeof<'a>) |> fst ).Name``` – philsquared Jan 26 '15 at 17:35
-
1Notice that this is a very slow approach, You'd definitely want to cache the results in order to get some performance milage out of this – Carlo V. Dango Jan 28 '16 at 15:23
@DanielAsher's answer works, but to make it more elegant (and fast? because of the lack of reflection for one of the methods), I would do it this way:
type Beverage =
| Coffee
| Tea
static member ToStrings() =
Microsoft.FSharp.Reflection.FSharpType.GetUnionCases(typeof<Beverage>)
|> Array.map (fun info -> info.Name)
override self.ToString() =
sprintf "%A" self
This answer supplies additional information and solutions to the top answer.
I just now had a case where the top answer did not work. The problem was that the value was behind an interface, and then I would sometimes get the case name (Coffee or Tea), but mostly only the type name (Beverage). I don't understand why. I'm on .NET 5.0.
I changed the function to this, and then it worked as expected on my interfaced DU, always giving me the case name.
open FSharp.Reflection
let GetUnionCaseName (x: obj) =
match FSharpValue.GetUnionFields(x, x.GetType()) with
| case, _ -> case.Name
I am aware that this is similar to other answers here, but this is not a member function, and so I guess should work on any DU, whether behind interfaces or not. I haven't tested what happens if used on a non-DU type.
type IMessage = interface end
type Beverage = Coffee | Tea
type Car =
| Tesla of model:string
| Ford
interface IMessage
type MySingleCase = MySingleCase of string
type SingleCase2 = SingleCase2 of string interface IMessage
let m1: Beverage = Coffee
let m2: IMessage = (Tesla "Model 3") :> IMessage
let m3 = MySingleCase "x"
let m4 = SingleCase2 "x" :> IMessage
printfn "%s" (GetUnionCaseName m1) // Coffee
printfn "%s" (GetUnionCaseName m2) // Tesla
printfn "%s" (GetUnionCaseName m3) // MySingleCase
printfn "%s" (GetUnionCaseName m4) // SingleCase2

- 3,445
- 26
- 35
I would like to propose something even more concise:
open Microsoft.FSharp.Reflection
type Coffee = { Country: string; Intensity: int }
type Beverage =
| Tea
| Coffee of Coffee
member x.GetName() =
match FSharpValue.GetUnionFields(x, x.GetType()) with
| (case, _) -> case.Name
When union case is simple, GetName()
may bring the same as ToString()
:
> let tea = Tea
val tea : Beverage = Tea
> tea.GetName()
val it : string = "Tea"
> tea.ToString()
val it : string = "Tea"
However, if union case is fancier, there will be a difference:.
> let coffee = Coffee ({ Country = "Kenya"; Intensity = 42 })
val coffee : Beverage = Coffee {Country = "Kenya"; Intensity = 42;}
> coffee.GetName()
val it : string = "Coffee"
> coffee.ToString()
val it : string = "Coffee {Country = "Kenya"; Intensity = 42;}"

- 1,814
- 15
- 29