0

I want to make a copy of an object that is of a discriminated union type, with one or two particular fields assigned different values and any other fields copied straight across.

The tricky part is that I'm trying to write a function to do this, that will keep working unchanged even as more cases are added to the union, so I can't use match; instead, I'm looking for a solution that uses reflection to examine the fields of the particular case. Here's what I have so far on the reverse side, extracting values from an object regardless of its exact type:

let case a =
    match FSharpValue.GetUnionFields (a, typeof<Term>) with
    | info, _ ->
        info

let unpack a =
    let fields = List.ofSeq ((case a).GetFields ())
    List.collect
        (fun (field: PropertyInfo) ->
            let t = field.PropertyType
            if t = typeof<Term> then
                [field.GetValue a :?> Term]
            elif t.IsGenericType && t.GenericTypeArguments.[0] = typeof<Term> then
                field.GetValue a :?> Term list
            else
               []
        )
        fields

And I'm trying to write a function that starts off something like:

let pack a xs =
    let fields = List.ofSeq ((case a).GetFields ())
    ...

It occurred to me to try to use MemberwiseClone but that's protected, so can't be used outside subclasses. I'm probably looking for something like 'create a new object of this type, then step through the fields copying across or filling in values as appropriate' though I'm not quite sure what 'this type' would be, since GetType doesn't work on discriminated unions. What's the best way to go about it?

rwallace
  • 26,045
  • 30
  • 102
  • 195

1 Answers1

5

This is actually a lot easier than it looks (ignoring the usual issues with Reflection).

You are already using FSharpValue.GetUnionFields to get the information about the union case and to get the values of the current union. There is also FSharpValue.MakeUnion which takes those information and gives you a union value:

type Term = 
  | Foo of int * string * bool

let a = Foo(42, "answer", false)

let case, values = FSharpValue.GetUnionFields(a, typeof<Term>) 
values.[2] <- box true
FSharpValue.MakeUnion(case, values) :?> Term
Tomas Petricek
  • 225,798
  • 19
  • 345
  • 516