Structs, Nullness and C# Interop
Interop tips: F# lists and options are awkward from C#. Expose arrays, IEnumerable, and nullable/Option conversions at boundaries. Mark records [<CLIMutable>] for serializers and ORMs that need setters and a default constructor.
// Structs (value types)
[<Struct>]
type Point3D =
val X: float
val Y: float
val Z: float
new(x, y, z) = { X = x; Y = y; Z = z }
[<Struct>]
type Pixel = { R: byte; G: byte; B: byte } // struct record
// Nullness (F# 9 with <Nullable>enable</Nullable>)
let maybeName: string | null = null
let length =
match maybeName with
| null -> 0
| name -> name.Length // name is non-null here
let fromDotNet = Option.ofObj (System.Environment.GetEnvironmentVariable "MAYBE")
// Calling C# from F#: just reference the assembly and use classes naturally
let sb = System.Text.StringBuilder()
sb.Append("x").Append(1) |> ignore
let list = System.Collections.Generic.List<int>()
list.Add 5
let tryParse = System.DateTime.TryParse "2025-10-24" // out params become tuple returns: (bool * DateTime)
// Exposing F# to C#: prefer classes, methods with tupled arguments, interfaces, and Func/Action
type Calculator() =
member _.Add(a: int, b: int) = a + b // C#: new Calculator().Add(1, 2)
member _.Apply(f: System.Func<int, int>, x: int) = f.Invoke x
[<CompiledName("ComputeTotal")>]
let computeTotal (items: decimal list) =
…