Interop with C# and .NET
Prerequisites: Classes and members
F# code can use any .NET library and be used from C#. A few differences between the two languages are worth knowing.
null from .NET
F# types can't be null, but .NET methods can return it. Convert at the
boundary:
let envValue name =
System.Environment.GetEnvironmentVariable name |> Option.ofObj
printfn "%A" (envValue "SURELY_NOT_DEFINED_12345")
let maybeText: string = null
match maybeText with
| null -> printfn "got null"
| text -> printfn "got %s" text
let nullableAge = System.Nullable<int>(42)
printfn "%A" (Option.ofNullable nullableAge)
None
got null
Some 42
Option.ofObj turns a possibly-null reference into an option;
Option.ofNullable does the same for Nullable<T> values. F# 9 added
optional nullable reference types (<Nullable>enable</Nullable> in
the project, then string | null in signatures) so the compiler can
track nulls for you.
Delegates, Func, and LINQ
F# lambdas convert automatically to .NET delegates like Func and
Action when passed to a method:
open System.Linq
let numbers = [ 5; 3; 8; 1 ]
let big = numbers.Where(fun n -> n > 2).OrderBy(fun n -> n).ToList()
printfn "%A" (List.ofSeq big)
let square = System.Func<int, int>(fun x -> x * x)
printfn "%d" (square.Invoke 7)
[3; 5; 8]
49
LINQ works, but the F# List/Seq functions are usually more natural
in F# code.
Extension methods
open System.Runtime.CompilerServices
type System.String with
member s.Shout() = s.ToUpper() + "!"
[<Extension>]
type StringExtensions =
[<Extension>]
static member Whisper(s: string) = s.ToLower() + "..."
printfn "%s" ("hello".Shout())
printfn "%s" ("HELLO".Whisper())
HELLO!
hello...
type X with member ... is an F# type extension, visible to F#
code. The [<Extension>] form produces a real .NET extension method
that C# can use as well.
Making F# friendly to C#
When a library will be called from C#:
- Put public types in a namespace and expose methods on classes or modules — C# sees F# modules as static classes.
- Use tupled parameters for public methods, and .NET collection types
(
IEnumerable<T>, arrays,IReadOnlyList<T>) rather than F# lists. - Return
Task<T>rather thanAsync<T>. - Avoid exposing
Optionand discriminated unions in the public API, or add helper members — C# can use them, but awkwardly. [<CLIMutable>]on a record adds a parameterless constructor and setters, which some serializers and ORMs (like Entity Framework) require.
Calling C# from F#
Reference the C# project (dotnet add reference ../MyLib/MyLib.csproj)
and use its types as you would any .NET library. Methods are called with
tupled arguments, out parameters become tuples (as with TryParse),
and properties are set with <- or with named arguments in the
constructor call:
let builder = System.UriBuilder(Scheme = "https", Host = "example.com", Path = "docs")
printfn "%s" (builder.Uri.ToString())
https://example.com/docs
The C# course is a good companion: most .NET documentation shows C# first.