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) =
…