F# Guide — .NET Interop: Calling C# Code from F#
In this tutorial, you will learn about F# Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
F# interoperates with C# and the .NET ecosystem by directly calling .NET APIs, creating objects, implementing interfaces, and consuming C# libraries with minimal friction.
What You'll Learn
- Calling .NET methods from F#
- Creating and using .NET objects
- Implementing C# interfaces
- Handling null values
- Exposing F# code to C#
Why It Matters
The entire .NET ecosystem is available from F#. You can use any C# library, framework, or tool while writing F# code.
Real-World Use
Using ASP.NET Core, Entity Framework, Azure SDKs, and NuGet packages from F# applications.
flowchart LR
A[".NET Interop"] --> B["Calling .NET APIs"]
B --> C["Creating Objects"]
C --> D["Interfaces"]
D --> E["C# Consumers"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Calling .NET Methods
open System
// Static methods
let now = DateTime.Now
let parsed = DateTime.Parse("2024-01-15")
let guid = Guid.NewGuid()
// Instance methods
let s = "hello world"
let upper = s.ToUpper()
let replaced = s.Replace("hello", "hi")
Creating .NET Objects
open System.Collections.Generic
// Constructor
let list = List<int>() // C# List<int>
list.Add(1)
list.Add(2)
// With parameters
let dict = Dictionary<string, int>()
dict.["key"] = 42
// StringBuilder
let sb = System.Text.StringBuilder()
sb.Append("Hello")
sb.Append(" World")
let result = sb.ToString()
Properties and Events
open System.Net
// Properties
let client = new WebClient()
client.Headers.Add("User-Agent", "F#")
let data = client.DownloadString("http://example.com")
// Events
let timer = new System.Timers.Timer(1000.0)
timer.Elapsed.Add(fun args ->
printfn "Tick at %A" args.SignalTime)
timer.Start()
Implementing Interfaces
type IComparer<'T> =
abstract Compare: 'T * 'T -> int
// Object expression implementation
let reverseComparer =
{ new IComparer<int> with
member _.Compare(x, y) = compare y x }
// Full class implementation
type MyComparer() =
interface IComparer<int> with
member _.Compare(x, y) = compare y x
Handling Null
// Nullable values
let nullableInt: System.Nullable<int> = System.Nullable(42)
let hasValue = nullableInt.HasValue
let value = nullableInt.Value
// Option to Nullable
let toNullable opt =
match opt with
| Some x -> System.Nullable(x)
| None -> System.Nullable()
// Null checks
let getStringLength (s: string) =
if isNull s then 0
else s.Length
Exposing F# to C#
// F# class visible to C#
type FSharpCalculator() =
member _.Add(x: int, y: int) = x + y
member _.Subtract(x: int, y: int) = x - y
// F# record visible to C#
[<CLIMutable>]
type Person = {
Name: string
Age: int
}
// F# module with static members
[<AbstractClass; Sealed>]
type FSharpUtils =
static member Square x = x * x
Using NuGet Packages
// Add package: dotnet add package Newtonsoft.Json
open Newtonsoft.Json
type User = { Name: string; Email: string }
let user = { Name = "Alice"; Email = "a@example.com" }
let json = JsonConvert.SerializeObject(user)
let deserialized = JsonConvert.DeserializeObject<User>(json)
Common Mistakes
1. NullReferenceException
C# code may return null. Check with isNull or use Option.ofObj.
2. Mutable state surprises
C# objects are mutable. Assign to immutable bindings carefully.
3. Value type boxing
Be aware of boxing when passing F# values to APIs expecting object.
4. Event subscription leaks
Unsubscribe from events when done to prevent memory leaks.
5. Exception Handling
C# methods throw exceptions. Use try-with to handle expected exceptions.
Practice Questions
1. How do you call a .NET static method from F#?
Use ClassName.MethodName(args). For example: DateTime.Now.
2. How do you create a .NET object?
Use the constructor: ClassName(args) or new ClassName(args).
3. How do you implement an interface in F#?
Use object expressions: { new IInterface with member ... }.
Challenge: Create an F# class that implements IEnumerable
FAQ
{{< faq question="Can I use all NuGet packages with F#?" >} Yes. Any NuGet package targeting .NET Standard or .NET works with F#. {{< /faq >}}
{{< faq question="How do I handle IDisposable in F#?" >}
Use the use keyword: use reader = new StreamReader(path).
{{< /faq >}}
{{< faq question="Can F# code be used from C#?" >} Yes. F# classes, records (with CLIMutable), and modules compile to standard .NET types visible from C#. {{< /faq >}}
{{< faq question="How do I access indexers?" >}
Use .[index] syntax for .NET indexers. Example: list.[0].
{{< /faq >}}
{{< faq question="What is object expression?" >} A syntax to create an ad-hoc implementation of an interface without defining a named class. {{< /faq >}}
Mini Project
Create an F# wrapper around a C# library:
open System.Net.Http
open System.Text.Json
type ApiClient(baseUrl: string) =
let client = new HttpClient()
do client.BaseAddress <- System.Uri(baseUrl)
member this.GetUserAsync userId = task {
let! response = client.GetAsync($"/users/{userId}")
response.EnsureSuccessStatusCode() |> ignore
let! json = response.Content.ReadAsStringAsync()
return JsonSerializer.Deserialize(json)
}
member this.CreateUserAsync name email = task {
let body = JsonSerializer.Serialize({| Name = name; Email = email |})
let content = new StringContent(body, System.Text.Encoding.UTF8, "application/json")
let! response = client.PostAsync("/users", content)
return response.IsSuccessStatusCode
}
interface System.IDisposable with
member this.Dispose() = client.Dispose()
What's Next
Now that you understand .NET interop, explore Fable for compiling F# to JavaScript.
| Topic | Description | Link |
|---|---|---|
| F# Fable | F# to JavaScript | {{< ref "28-fable" >}} |
| F# Paket | Package management | {{< ref "29-paket" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro