all Technical posts

Improve F#/C# Interoperability with Simple Interventions

Using F# in C# code or the other way around gets easier with each new .NET version. The automatic project loading of the latest version shows that the interoperability between these two languages is something that we will see more and more of. This post will go over some minor code interventions you can take to make the code exchange more developer-friendly.

F# to C#: expose as common types

The common case is that pure F# code is used in unpure C# code. F# is a great way to design strict rules and application core code, while C# is great at handling nifty and cumbersome interactions with the outside world. Let’s go over some practical examples. The common idea is that we should make sure that we don’t expose language-specific types but rather use types that both code languages are familiar with. ‘Speaking their language’ is one of the key ideas here.

CompileName attribute

F# uses camelCase for functions, while C# uses PascalCase for methods. When you want to expose a function, you can easily apply the CompileNameAttribute to your function and give it the PascalCase alternative.

Lists, options, delegates and async operations

The F#-specific List, Option and Async types work well in C#. Keep in mind that you need to expose an alternative. For List types, this can be IEnumerable; for Async, this could be Task. If you want to take in a function, make sure to use a delegate so that C# can easily use Func and Action. An interesting type to expose is Option. As C# doesn’t have such an optional type, we can’t use an alternative. Usually, this is fixed by exposing a Try... function with an output variable. This is a common practice in C# to avoid exception handling and is a good alternative for exposing F# Option types.

This example shows another good practice: using tupled methods stabilizes the exposed signature and clearly shows other code that it is for C# usages.

💡 Speaking of which, if you want to force your exposed F# code for only C# usages, you can add CompilerMessage("Not designed for F#", 1001, IsHidden = true) to your code. The IsHidden property will make sure that the code is compiled but will not show up when developing F# code. This is a very cool feature that helps to organize your code, as the F# API won’t get overwhelmed with interop code, while the C# API can still benefit from it.

Function composition as extensions

A final tip is to expose function composition as extension methods. F# is built for composition, but C# is not. What C# has is an extension method that allows you to ‘pipe’ results. Like using types that the C# environment understands, it is a good alternative to helping the C# developer interact with your code without overwhelming them.

The following example shows you a combination of all the practices in this section: naming, argument checking, interop hiding from F#, exposing known types and functions as extensions:

Notice that the IEnumerable is a common type in C# with many LINQ extensions. Function composition could be treated the same way if you want to expose it in C#. Since the IEnumerable and seq are interchangeable, we don’t need any additional conversions here. The Action will need to be called with the Invoke method, to step away from the object and use the function instead. It is a clear indication that you step into the realm of functions instead of objects. Placing the extension in a namespace that will be easily discovered is also a way to help your C# developers.

C# to F#: forcing good practice

In certain cases, you want to use C# code in F#. An example is when you want to use F# as a ‘pipeline component’ between two C# code projects. Maybe F# is a better language to solve a specific problem and you would like to take a detour via F#.
To use C# code easier in an F# environment, there are some best practices to take into account. You’ll notice that most practices will make your C# code only better.

The constructor takes it all

Regular class properties are supported in F#, but setting properties is not very clean and easy to use code (with reason!). Changing a property value is seen as a ‘side effect’, and should be treated with caution. Immutability is by default present in F#, so it’s a good practice to write the classes that you use in F# as immutable classes. What this means is that you should limit the use of property settings and make sure that the class constructor takes all the necessary information from the start.

Don’t trust external classes

While C# is working on removing null from being a ‘valid valid’, we still could have situations where null values happen. When you take in a C# type in F# code, don’t assume that it will be safe(r) than any other F# type (records, discriminated unions). Check for null with isNull and throw with nullArg like general C# code. After that, you can be a little safer, but be still aware that you’re using an unsafe type. This is also the case with C# record classes. They are still treated like regular classes in F# and are not transferable to F# records, so check for null values.

Don’t overdo method overloading

As F# highly leans on implicit generic constructs, method overloading could be a problem. Ambiguous errors could occur if multiple overloads are ‘valid’ to be used but the type system doesn’t know what type to use. Use factory methods instead, otherwise F# will have to add a lot of argument names to make the distinction between method overloads clear.

Conclusion

Combining these two .NET languages is a great way to get best of both worlds. We should, however, be cautious of what we expose to the other language, as the way they treat data and actions are quite different. A good common practice is to keep it simple and use the basic syntax and expressions in your API. Both are .NET languages so they both support all the underlying types. The exchange should happen on that level. You’ll see that these languages are not battling against each other, but are making each other great.

💡 My own FPrimitive library is proof that a .NET package can support both languages without taking back on the exposed functionality. Please consider supporting both languages for your next library so we can work together on a more inclusive code space.

Thanks for reading.
Stijn

Subscribe to our RSS feed

Hi there,
how can we help?

Got a project in mind?

Connect with us

Let's talk

Let's talk

Thanks, we'll be in touch soon!

Call us

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!