Skip to content
AgileLogics
5 min read

Mutable .NET Collections

Prerequisites: Maps and sets

F#'s own collections are immutable. For performance-critical code, or when talking to .NET APIs, you'll also use the mutable collections from System.Collections.Generic.

ResizeArray

ResizeArray<'T> is F#'s name for .NET's List<T> — a growable array (not to be confused with the F# immutable list):

let items = ResizeArray<string>()
items.Add "apple"
items.Add "banana"
items.AddRange [ "cherry"; "date" ]
items.Remove "banana" |> ignore
items.Insert(0, "avocado")

printfn "%d items: %s" items.Count (String.concat ", " items)
printfn "%s" items[1]

let lengths = items |> Seq.map _.Length |> List.ofSeq
printfn "%A" lengths
4 items: avocado, apple, cherry, date
apple
[7; 5; 6; 4]

Dictionary

open System.Collections.Generic

let stock = Dictionary<string, int>()
stock["apples"] <- 10
stock["pears"] <- 4
stock["apples"] <- stock["apples"] - 3

printfn "%d" stock["apples"]

match stock.TryGetValue "plums" with
| true, n -> printfn "plums: %d" n
| false, _ -> printfn "no plums"

for KeyValue (fruit, count) in stock do
    printfn "%s=%d" fruit count

let readOnly = dict [ "a", 1; "b", 2 ]
printfn "%d" readOnly["b"]
7
no plums
apples=7
pears=4
2

dict builds a read-only dictionary from pairs — quick lookup tables without mutation.

HashSet

open System.Collections.Generic

let seen = HashSet<string>()
for word in "the cat saw the other cat".Split ' ' do
    if not (seen.Add word) then
        printfn "duplicate: %s" word

printfn "%d unique" seen.Count
duplicate: the
duplicate: cat
4 unique

Add returns false when the item was already present.

Immutable or mutable?

Need Choose
Default choice, sharing data safely list, Map, Set
Many lookups or inserts in a hot loop Dictionary, HashSet
Building up a large collection step by step ResizeArray, then Seq.toList if needed
Passing data to .NET APIs whatever the API expects (List<T>, arrays)

A good pattern is to use a mutable collection inside a function for speed and return an immutable result, so callers never see the mutation. Note that Dictionary and ResizeArray are not thread-safe; use System.Collections.Concurrent types when several threads write.