F# - C# Interop
Tags: fsharp, programming, beginners, dotnet
Published: 2022-02-24 09:52:10

## 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#.

> :warning: **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

```cs
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
```fsharp
namespace WordReverserConsoleAppFsharp

open System.Threading
open WordReverserLibraryCsharp

module 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](https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/async#asyncawaittask)

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
```fsharp
namespace WordReverserLibraryFsharp

open System.Threading
open System.Threading.Tasks

module 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
```cs
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](https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/async#asyncstartastask)
 
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
```fsharp
namespace FsharpLibraryComplete

open System.Threading
open System.Threading.Tasks

module 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
```cs
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 14
Unhandled 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`.