Search code examples
c#f#portable-class-library

How to achieve `FSharpValue.GetUnionFields` in a C# PCL (Profile 259)


I'm in a C# shared project trying to find the PCL (Profile 259) equivalent for FSharpValue.GetUnionFields.

In object browser via the C# project, I see

namespace Microsoft.FSharp.Reflection
{
    [AutoOpen]
    [CompilationMapping(SourceConstructFlags.Module)]
    public static class FSharpReflectionExtensions
    {
        public static Tuple<UnionCaseInfo, object[]> FSharpValue.GetUnionFields.Static(object value, Type unionType, [OptionalArgument] FSharpOption<bool> allowAccessToPrivateRepresentation);
    }
}

This appears to be what I'm looking for, but I'm unable (or don't know how) to call it from C#. Via F#, if I open the namespace, I can call the extension FSharpValue.GetUnionFields. FSharpValue.GetUnionFields does not compile from a c# PCL. I'm not experienced with F# so it could be I'm just lacking some important piece of knowledge related to F# - C# interop?

For reference, this is what I see from a F# pcl.

[<AutoOpen>]
module Microsoft.FSharp.Reflection.FSharpReflectionExtensions
open Microsoft.FSharp.Reflection

val GetUnionFields : value:obj * unionType:System.Type * ?allowAccessToPrivateRepresentation:bool -> UnionCaseInfo * obj []

Repro project here: https://github.com/kennethito/StackOverflowReferences/tree/master/FSharpValue-GetUnionFields


Solution

  • Again, this requires using reflection. Since it's a PCL, it's particularly nasty, as the actual version of FSharp.Core loaded at runtime is the one that will matter.

    The following should work:

    public static Tuple<UnionCaseInfo, object[]> TestIt()
    {
        var option = new FSharpOption<int>(123);
    
        MethodInfo method;
        try
        {
            // If "4.4.0.0" is loaded at runtime, get directly
            var t = typeof(FSharpValue);
            method = t.GetRuntimeMethods().First(mi => mi.Name == "GetUnionFields");
        }
        catch 
        {
            var t = typeof(FSharpReflectionExtensions);
            method = t.GetRuntimeMethods().First(mi => mi.Name == "FSharp.Value.GetUnionFields.Static");
        }
        return (Tuple<UnionCaseInfo, object[]>)method.Invoke(null, new object[] { option, option.GetType(), null });
    }
    

    This tries to find the method directly on the type (how it's specified in FSharp.Core 4.4), and falls back to the PCL structure (as an extension method).

    The following C# console application shows it working:

    static void Main(string[] args)
    {
        Tuple<UnionCaseInfo, object[]> results = CsharpPortable.Test.TestIt();
        var uci = results.Item1;
        Console.WriteLine("{0}:", uci.Name);
        foreach (var pi in uci.GetFields())
        {
            Console.WriteLine("Property: {0}", pi.Name);
        }
        Console.ReadKey();
    }