An Interest In:
Web News this Week
- March 20, 2024
- March 19, 2024
- March 18, 2024
- March 17, 2024
- March 16, 2024
- March 15, 2024
- March 14, 2024
F - C Interop
What is Interop
Interoperability is the ability of two different systems to communicate with each other. In this case, it would be a C# and F# application communicating. Because they compile down to the same Intermediate Language we can reference F# projects in C# projects and vice versa.
Why is this useful
One of the benefits is that if you are planning to migrate your codebase from C# to F#, you can do it gradually by introducing some F# code without discarding any of the existing code base.
Sometimes we want to take advantage of the C# world, i.e. a lot of existing nuget packages are written in C# and we don't want to rewrite them in F#.
Please note that all the observations listed on this page are valid for .NET 5 and below.
Example F# Application using C# library
In this example, we are going to create an F# application that uses a C# library.
The library simulates an IO operation with await Task.Delay(300, cancellationToken);
and reverses the words sent.
C# Library
namespace WordReverserLibraryCsharp{ public static class WordReverser { public static async Task<string> WordReverserAsync(string sentence, CancellationToken cancellationToken) { await Task.Delay(300, cancellationToken); var words = sentence.Split(' '); Array.Reverse(words); return $"{string.Join(" ", words)}"; } }}
F# Application
namespace WordReverserConsoleAppFsharpopen System.Threadingopen WordReverserLibraryCsharpmodule WordReverserModule = let wordReverser (sentence: string) : Async<unit> = async { let cancellationTokenSource = new CancellationTokenSource(800); let! reversedWords = WordReverser.WordReverserAsync(sentence, cancellationTokenSource.Token) |> Async.AwaitTask printfn ($"{reversedWords}") } Async.Start(wordReverser("one two three")) Async.RunSynchronously(Async.Sleep(1000))
Observations
To consume the C# library from F#, we have to convert the Task<T>
returned from the library to Async<T>
using Async.AwaitTask
which waits for the Task to complete and returns its result as Async<T>
. Read about Async.AwaitTask here
Just before the line Async.Start(wordReverser("one two three"))
we still haven't invoked the operation, only done the setup to call wordReverser
, to start the operation we can use Async.Start(wordReverser("one two three")
.
Example C# Application using F# library
In this example we are doing the opposite of the previous one: we create a C# application that uses an F# library.
F# Library
namespace WordReverserLibraryFsharpopen System.Threadingopen System.Threading.Tasksmodule WordReverserModule = let wordReverser (sentence: string, cancellationToken: CancellationToken) : Task<string> = let reverser = async { do! Async.Sleep(300) return sentence.Split [| ' ' |] |> Array.rev |> String.concat " " } Async.StartAsTask(reverser, TaskCreationOptions.None, cancellationToken)
C# Application
using WordReverserLibraryFsharp;namespace WordReverserConsoleAppCsharp{ internal static class Program { private static async Task Main() { var cancellationTokenSource = new CancellationTokenSource(400); var reversedWords = await WordReverserModule.wordReverser("one two three", cancellationTokenSource.Token); Console.WriteLine(reversedWords); } }}
Observations
We can't use F# Async
in C#, but the F# library can return a Task
so that the C# application can consume it without any issues. We can achieve this by using Async.StartAsTask
in .NET 5 and below.
Read about Async.StartAsTask here
It's good practice for any Async
work in C#, to always pass a CancellationToken as an argument, while the F# code does not necessarily need one as the CancellationToken propagation is controlled by how the asynchronous work is kicked off and as a result, CancellationTokens may or may not be propagated. For example Async.Sleep
doesn't accept a CancellationToken as a parameter.
Example C# Application using F# library handling exceptions
In this example, we are going to use an F# library from a C# application, handling exceptions and passing a CancellationToken.
F# Library
namespace FsharpLibraryCompleteopen System.Threadingopen System.Threading.Tasksmodule FsharpLibraryCompleteModule = let wordReverser (sentence: string, raiseException: bool, cancellationToken: CancellationToken) : Task<string> = let reverser = async { do! Async.Sleep(300) if(raiseException) then raise (System.Exception("wordReverser threw Exception")) return sentence.Split [| ' ' |] |> Array.rev |> String.concat " " } Async.StartAsTask(reverser, TaskCreationOptions.None, cancellationToken)
C# Application
using FsharpLibraryComplete;namespace ConsoleAppCsharpComplete;internal static class Program{ private static async Task Main() { var cancellationTokenSource = new CancellationTokenSource(500); string reversedWords; try { reversedWords = await FsharpLibraryCompleteModule.wordReverser("one two three", true, cancellationTokenSource.Token); } catch (Exception e) { Console.WriteLine(e); throw; } Console.WriteLine(reversedWords); }}
Console output
System.Exception: someAsyncFunction threw Exception at FsharpLibraryComplete.FsharpLibraryCompleteModule.wordReverser@12-1.Invoke(Unit _arg1) in C:\projects\InteropPlayGround\Docker\ConsoleAppCsharpComplete\FsharpLibraryComplete\FsharpLibraryComplete.fs:line 13 at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvokeNoHijackCheck[a,b](AsyncActivation`1 ctxt, b result1, FSharpFunc`2 userCode) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 464 at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 104--- End of stack trace from previous location --- at ConsoleAppCsharpComplete.Program.Main() in C:\projects\InteropPlayGround\Docker\ConsoleAppCsharpComplete\ConsoleAppCsharpComplete\Program.cs:line 14Unhandled exception. System.Exception: someAsyncFunction threw Exception at FsharpLibraryComplete.FsharpLibraryCompleteModule.wordReverser@12-1.Invoke(Unit _arg1) in C:\projects\InteropPlayGround\Docker\ConsoleAppCsharpComplete\FsharpLibraryComplete\FsharpLibraryComplete.fs:line 13 at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvokeNoHijackCheck[a,b](AsyncActivation`1 ctxt, b result1, FSharpFunc`2 userCode) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 464 at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 104--- End of stack trace from previous location --- at ConsoleAppCsharpComplete.Program.Main() in C:\projects\InteropPlayGround\Docker\ConsoleAppCsharpComplete\ConsoleAppCsharpComplete\Program.cs:line 14 at ConsoleAppCsharpComplete.Program.<Main>()
Observations
Running the code as it is, will raise an exception from the F# library that gets caught by the C# application.
Please note that if the Task gets cancelled before raising the exception on the F# library, the C# application will catch the exception as System.Threading.Tasks.TaskCanceledException: A task was canceled.
Conclusion
It's not particularly difficult to use F# libraries in a C# application and vice versa, but there are a few things that we have to keep in mind, i.e. C# does not work with F# Async
, instead, we have to add additional steps to convert Async
into a Task
.
Original Link: https://dev.to/amedeov/f-c-interop-2a42
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To