C# – F# Interop (2026 edition)

One of the great features of .NET is that you can mix its programming languages (C#, F#, VB.NET) in a single solution and that assemblies written in one language can call assemblies written in other languages. This is great, especially when starting with F#. A team does not need to switch everything at once, but can keep using the existing C# code and use Interop to call C# from F# or vice versa.
In this article, we take a look at the Interop story in 2026. Spoilers: it works great.

Mixing C# and F# in the same solution

You can mix C# and F# code in the same solution, but for each assembly/project, you have to decide whether it’s a C# or F# assembly. The (simplified) architecture of our backend system looks like this:

The ASP.NET controllers are distributed in several assemblies and referenced by a central assembly that holds the StartUp and Program classes. Referencing other assemblies containing controllers is enough for ASP.NET to find them. These assemblies can either be F# or C#. We have an API assembly for every sub-system. A sub-system is a cohesive part of our domain. Sub-systems can talk to each other, but they are loosely coupled.

The business logic for every sub-system is put into a dedicated assembly – either C# or F#.

We can keep each vertical slice in either C# or F#, minimising Interop. We only need Interop when we talk horizontally between sub-systems. Interop works great, but it’s an additional effort.

Can’t you slice your codebase into F# or C# sub-systems? Do not worry, you can, of course, still use Interop and have a (wild) mix of C# or F# assemblies referencing each other. The tricky part is typically deciding where to define shared types – and not ending up with cyclic references.

Calling C# from F# – the very easy direction

Calling C# from F# is really easy, just do it. F# understands C# really well. F# supports methods, properties, extension methods, etc., out of the box.

Calling F# from C# – the easy direction

Most F# things are also easy to call from C#, but there are some exceptions. However, this is the more interesting direction because, typically, we want to rely on F#’s strengths in business logic to model the domain with its strong type system and discriminated unions, and on its simple function composition to implement business rules. So F# on the inside, and C# on the outside (frameworks typically think in C# in the .NET space).

Records

F# records feel like C# records in C#.

F# record
Creating F# record in C#.

Discriminated Unions

Creating a discriminated union value depends on whether the case has additional fields.

F# discriminated union
Creating discriminated union values in C#

Pattern matching in C# on F# unions is straight-forward for cases with additional fields. For simple cases, we can’t directly match on the case, but need to use the when syntax combined with an IsXyz property:

Pattern matching in C# on F# unions.

Also, notice that in C# we always have to add an _ => pattern.

Modules

C# can call functions from F# modules directly. Maybe you already noticed that sometimes modules in F# have a Module suffix when called from C#. This is the case when you have a module and a type with the same name inside the same module/namespace in F#. Since C# does not support this scenario, the compiler adds the Module suffix.

For consistency reasons, we want a Module suffix for all modules. So we add an attribute (CompilationRepresentationAttribute) to the module to tell the compiler always to add the Module suffix.

Module suffix

As a side note, the RequiredQualifiedAccess attribute tells the compiler only to accept calls to the functions inside the module when it is qualified with the module name: Library.function.

Functions

Thanks to the compilers, calling functions is straightforward – even functions with multiple arguments:

F# functions with differentnumbers of arguments.
Calling F# functions from C#.

If you want, you can give functions CamelCase names, too:

Specifying a different compiled name seen by C#.
Calling a function by its compiled name.

This makes the code feel more like C#.

Lists

We use the library FSharpx to make creating F# lists easy. The library provides the ToFSharpList extension method.

An F# function with an F# list as an argument.
Creating an F#list from a C# list.

Functions

Functions in F# are not Func<>s, so we need to convert them when passing C# lambdas to F#. Again, we use a helper from FSharpx.

An F# function with a function argument.
Using FromFunc to convert the function argument.

Measures

F# supports units of measure. With a small trick, even for non-numeric values (FSharp.UMX).

C# does not, the measure get’s simply erased.

A GUID with a measure to make it type safe.
C# sees it as a simple GUID.

Async Workflows

We can call async workflows directly from C# – no need to wrap every async workflow in a task computation expression or task call in F#.

An async function in F# (async workflow)
Calling async workflow directly from C#.

FsToolkit.ErrorHandling primarily provides functionality for Options, Results, and Validation, but it also includes some handy async helper functions.

StartImmediateAsTask is closest to a Task await, but there are other options as well.

Partial Application

We can even call partially applied functions from C# – it’s not nice, but it works. We can use the Invoke method.

A partially applied function.
Calling a partially applied function.

Tuples

In F#, tuples by default are reference tuples; in C#, struct tuples. But both know the other:

Tuples in F#
Using the F# tuples in C#.

Options

To simplify working with options, we can again rely on FSharpx, which provides the ToFSharpOption extension method.

Options in F# (reference and value type)
Passing options to F# from C#.

Extension Methods

Sometimes, there is no nice way to call some F# functionality. Then, we can always add some extension methods to make it easier to be called from C#. Simply decorate an F# function with the Extension attribute:

Defining an extension method in F#
Using the extension method in C# as usual.

Don’t pass functions to C# this way

Make your life easy, don’t pass functions (as type function – a function without explicitly defined arguments) from F# to C#. Define the functions used by C# with an argument:

Summary

Mixing F# and C# in a codebase works. While not always ideal, it provides a straightforward way to introduce F# into a C# codebase.

About the author

Urs Enzler

1 comment

By Urs Enzler

Recent Posts