F# supports .NET object-oriented programming: classes, interfaces, structs, inheritance, overloading and events. Types are defined with type; a type is a class when it has members or a constructor, or when it says class … end (a type with only { } fields is a record).
Classes
type Counter(start: int, ?step: int) =
let mutable count = start // a private field
let step = defaultArg step 1
do printfn "created at %d" start // runs when an object is made
member this.Count = count // a read-only property
member this.Increment() = count <- count + step
member val Name = "counter" with get, set // an auto-property
static member Create() = Counter(0)
new() = Counter(0) // an additional constructor
override this.ToString() = $"Counter({count})"
let c = Counter(5, step = 2)
c.Increment()
c.Name <- "main"
let and do go first, then members.- Primary constructor.
type C(args) =. Theletanddobindings that follow run in order when an object is made;letvalues and functions are private to the class.type C(args) as this =gives the constructor a name for the object (using it before construction ends is checked at run time). - Other constructors.
new (args) = C(otherArgs)ornew (args) = { field = value }with explicit fields. Objects are made by calling the type like a function (C(1));new C(1)is the same and is expected forIDisposabletypes. - Members.
member self.Name args = expr; the name before the dot (this,x,_) is for this object. Methods take tupled or curried arguments; tupled ones can be overloaded by count and type and can use named, optional (?x: int) and[<ParamArray>]arguments. - Properties.
member this.P = expris a read-only property;member this.P with get () = … and set v = …is read-write;member val P = init with get, setis an auto-property, initialised once. - Static.
static member,static letandstatic dobelong to the type;static member valis a static auto-property. - Indexers.
member this.Item with get (i: int) = … and set (i: int) v = …makesobj.[i](andobj[i]) work. - Explicit fields.
val mutable x: intwith[<DefaultValue>]when the class has a primary constructor;valfields otherwise need an explicit constructor.
Inheritance and abstract members
[<AbstractClass>]
type Animal(name: string) =
member _.Name = name
abstract Speak: unit -> string // no body here
abstract Legs: int
default _.Legs = 4 // a body: a virtual member
type Dog(name: string) =
inherit Animal(name)
override _.Speak() = "Woof"
type Bird(name: string) =
inherit Animal(name)
override _.Speak() = "Tweet"
override _.Legs = 2
let a : Animal = Dog("Rex") :> Animal // an upcast
let d = a :?> Dog // a downcast
let isBird = a :? Bird // a type test
abstract declares a slot (with a signature and no body); default gives it a body in the declaring class; override replaces it in a subclass. A class with an abstract member that has no default must be marked [<AbstractClass>]. inherit Base(args) names the base class and calls its constructor; base.Member calls the base implementation. [<Sealed>] stops inheritance. Members are public unless marked.
Interfaces
type IShape =
abstract Area: float
abstract Scale: factor: float -> IShape
type Square(side: float) =
interface IShape with
member _.Area = side * side
member _.Scale f = Square(side * f) :> IShape
let s = Square(2.0) :> IShape // an interface is used through a cast
printfn "%f" s.Area
let named =
{ new IShape with // an object expression
member _.Area = 1.0
member this.Scale f = this }
An interface type has only abstract members (interface … end is optional). A class implements it with interface I with and its members; an interface member is called on a value of the interface type, so a cast is needed (:>, or (x :> I).Member). An object expression { new I with … } makes an object of an anonymous class that implements the interface or inherits a class, without a type definition. A type may implement an interface at different generic instantiations (F# 5), consume default interface members (F# 5), and static abstract members in interfaces are supported (F# 7).
Structs
[<Struct>]
type Vec(x: float, y: float) =
member _.X = x
member _.Y = y
member _.Length = sqrt (x * x + y * y)
[<Struct; IsReadOnly>]
type Id = { Value: int }
type S =
struct
val X: int
val Y: int
new(x, y) = { X = x; Y = y }
end
A struct is a value type: it is copied, cannot inherit (it may implement interfaces) and is not null. [<Struct>] makes a class-like type, a record or a union a struct; struct … end with val fields is the explicit form. [<IsReadOnly>] marks a readonly struct, [<IsByRefLike>] a byref-like one (for Span-style types). A mutable struct value is changed in place only when it is a mutable binding, not when it is a property result.
Type extensions and operators
type System.String with // an intrinsic or optional extension
member s.Shout() = s.ToUpper() + "!"
module StringExt =
type System.Int32 with
static member Double(n: int) = n * 2
type Money(amount: decimal) =
member _.Amount = amount
static member (+) (a: Money, b: Money) = Money(a.Amount + b.Amount)
static member (~-) (a: Money) = Money(-a.Amount)
static member Zero = Money(0m)
A type extension in the same file and namespace as the type is intrinsic and is compiled into it; in another module it is optional and visible where that module is opened. Extension members can be used from F# only, unless they carry [<Extension>] (a static member in a class marked the same way, for C#). Operator members use the names such as op_Addition in other languages; the prefix forms are (~-) and (~+).
Events and disposal
type Notifier() =
let changed = new Event<string>()
[<CLIEvent>]
member _.Changed = changed.Publish // a .NET event
member _.Raise(s) = changed.Trigger s
type Resource() =
interface System.IDisposable with
member _.Dispose() = printfn "freed"
let work () =
use r = new Resource() // disposed at the end of the scope
printfn "working"
[<CLIEvent>] and a disposable resource bound with use.Access
public (the default), internal (the assembly) and private (the enclosing type or module) can be written before a member, a let, a type or a constructor (type C private (x: int) = …) and, from F# 10, on the accessors of an auto-property (member val P = 1 with get, private set). protected is reserved by the lexer but not used (F# has no way to declare a protected member); a protected member of a .NET base class is used from a subclass (from F# 11 also from a closure). See modules, namespaces and access.
Not described
Known, not yet covered
- Reference: the rules for overloading resolution, optional and named arguments in full, explicit-field constructors and the
as thisinitialisation checks, events as first-class values (IEventand theEventmodule), the full list of attributes ([<DefaultValue>],[<ParamArray>],[<Extension>]and others are named, not described), delegates with members, and the F# 11 changes (direct delegates,OverloadResolutionPriority, required named arguments). - Reference: the documentation pages for classes, members, interfaces, inheritance and structs were not read line by line; the text above is written from their structure and from the language, and the examples were not compiled.
See also
- Generics and constraints – parameters of types and members
- .NET interop – using and defining .NET types