Suppose there is a F#
definitions:
type Either<'a,'b> = | Left of 'a | Right of 'b
let f (i : int) : Either<int, string> =
if i > 0
then Left i
else Right "nothing"
Function f
is used in C#
code:
var a = Library.f(5);
How the result value a
could be pattern matched for data constructors? Something like:
/*
(if a is Left x)
do something with x
(if a is Right y)
do something with y
*/
Using F# discriminated unions from C# is a bit inelegant, because of how they are compiled.
I think the best approach is to define some members (on the F# side) that will simplify using the types from C#. There are multiple options, but the one I prefer is to define TryLeft
and TryRight
methods that behave similarly to Int32.TryParse
(and so they should be familiar to C# developers using your F# API):
open System.Runtime.InteropServices
type Either<'a,'b> =
| Left of 'a
| Right of 'b
member x.TryLeft([<Out>] a:byref<'a>) =
match x with Left v -> a <- v; true | _ -> false
member x.TryRight([<Out>] b:byref<'b>) =
match x with Right v -> b <- v; true | _ -> false
Then you can use the type from C# as follows:
int a;
string s;
if (v.TryLeft(out a)) Console.WriteLine("Number: {0}", a);
else if (v.TryRight(out s)) Console.WriteLine("String: {0}", s);
You lose some of the F# safety by doing this, but that's expected in a language without pattern matching. But the good thing is that anybody familiar with .NET should be able to use the API implemented in F#.
Another alternative would be to define member Match
that takes Func<'a>
and Func<'b>
delegates and invokes the right delegate with the value carried by left/right case. This is a bit nicer from the functional perspective, but it might be less obvious to C# callers.
I'd define a Match
member taking the delegates to execute in each scenario. In F# you'd do it like this (but you could do something equivalent in a C# extension method, if desired):
type Either<'a,'b> = | Left of 'a | Right of 'b
with
member this.Match<'t>(ifLeft:System.Func<'a,'t>, ifRight:System.Func<'b,'t>) =
match this with
| Left a -> ifLeft.Invoke a
| Right b -> ifRight.Invoke b
Now you should be able to do something like this in C#:
var result = a.Match(ifLeft: x => x + 1, ifRight: y => 2 * y);
From the 3.0 spec :
8.5.4 Compiled Form of Union Types for Use from Other CLI Languages
A compiled union type U has:
- One CLI static getter property UC for each null union case C. This property gets a singleton object that represents each such case.
One CLI nested type UC for each non-null union case C. This type has instance properties Item1, Item2.... for each field of the union case, or a single instance property Item if there is only one field. However, a compiled union type that has only one case does not have a nested type. Instead, the union type itself plays the role of the case type.
One CLI static method U.NewC for each non-null union case C. This method constructs an object for that case.
- One CLI instance property U.IsC for each case C. This property returns true or false for the case.
- One CLI instance property U.Tag for each case C. This property fetches or computes an integer tag corresponding to the case.
If U has more than one case, it has one CLI nested type U.Tags. The U.Tags typecontains one integer literal for each case, in increasing order starting from zero.
A compiled union type has the methods that are required to implement its auto-generated interfaces, in addition to any user-defined properties or methods.
These methods and properties may not be used directly from F#. However, these types have user-facing List.Empty, List.Cons, Option.None, and Option.Some properties and/or methods.
A compiled union type may not be used as a base type in another CLI language, because it has at least one assembly-private constructor and no public constructors.
If you can't change the F# api, using points 2 and 4 above you could do it something like this:
C#
class Program
{
static void Main(string[] args)
{
PrintToConsole("5");
PrintToConsole("test");
}
static void PrintToConsole(string value)
{
var result = test.getResult(value);
if (result.IsIntValue) Console.WriteLine("Is Int: " + ((test.DUForCSharp.IntValue)result).Item);
else Console.WriteLine("Is Not Int: " + ((test.DUForCSharp.StringValue)result).Item);
}
}
F#
namespace Library1
module test =
open System
type DUForCSharp =
| IntValue of int
| StringValue of string
let getResult x =
match Int32.TryParse x with
| true, value -> IntValue(value)
| _ -> StringValue(x)
This solution is convenient in that it also handles tuple DU cases by creating a new property for each item in the tuple.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.