As should be apparent, you should generally try to prefer functional-style code over object-oriented code in F#, but in some situations, you may need all the features of a fully fledged OO language ? classes, inheritance, virtual methods, etc.
So just to conclude this section, here is a whirlwind tour of the F# versions of these features.
Some of these will be dealt with in much more depth in a later series on .NET integration. But I won't cover some of the more obscure ones, as you can read about them in the MSDN documentation if you ever need them.
Classes and interfaces ##
First, here are some examples of an interface, an abstract class, and a concrete class that inherits from the abstract class.
// interface
type IEnumerator<'a> =
abstract member Current : 'a
abstract MoveNext : unit -> bool
// abstract base class with virtual methods
[<AbstractClass>]
type Shape() =
//readonly properties
abstract member Width : int with get
abstract member Height : int with get
//non-virtual method
member this.BoundingArea = this.Height * this.Width
//virtual method with base implementation
abstract member Print : unit -> unit
default this.Print () = printfn "I'm a shape"
// concrete class that inherits from base class and overrides
type Rectangle(x:int, y:int) =
inherit Shape()
override this.Width = x
override this.Height = y
override this.Print () = printfn "I'm a Rectangle"
//test
let r = Rectangle(2,3)
printfn "The width is %i" r.Width
printfn "The area is %i" r.BoundingArea
r.Print()
Classes can have multiple constructors, mutable properties, and so on.
type Circle(rad:int) =
inherit Shape()
//mutable field
let mutable radius = rad
//property overrides
override this.Width = radius * 2
override this.Height = radius * 2
//alternate constructor with default radius
new() = Circle(10)
//property with get and set
member this.Radius
with get() = radius
and set(value) = radius <- value
// test constructors
let c1 = Circle() // parameterless ctor
printfn "The width is %i" c1.Width
let c2 = Circle(2) // main ctor
printfn "The width is %i" c2.Width
// test mutable property
c2.Radius <- 3
printfn "The width is %i" c2.Width
Generics ##
F# supports generics and all the associated constraints.
// standard generics
type KeyValuePair<'a,'b>(key:'a, value: 'b) =
member this.Key = key
member this.Value = value
// generics with constraints
type Container<'a,'b
when 'a : equality
and 'b :> System.Collections.ICollection>
(name:'a, values:'b) =
member this.Name = name
member this.Values = values
Structs ##
F# supports not just classes, but the .NET struct types as well, which can help to boost performance in certain cases.
type Point2D =
struct
val X: float
val Y: float
new(x: float, y: float) = { X = x; Y = y }
end
//test
let p = Point2D() // zero initialized
let p2 = Point2D(2.0,3.0) // explicitly initialized
Exceptions ##
F# can create exception classes, raise them and catch them.
// create a new Exception class
exception MyError of string
try
let e = MyError("Oops!")
raise e
with
| MyError msg ->
printfn "The exception error was %s" msg
| _ ->
printfn "Some other exception"
Extension methods ##
Just as in C#, F# can extend existing classes with extension methods.
type System.String with
member this.StartsWithA = this.StartsWith "A"
//test
let s = "Alice"
printfn "'%s' starts with an 'A' = %A" s s.StartsWithA
type System.Int32 with
member this.IsEven = this % 2 = 0
//test
let i = 20
if i.IsEven then printfn "'%i' is even" i
Parameter arrays ##
Just like C#'s variable length "params" keyword, this allows a variable length list of arguments to be converted to a single array parameter.
open System
type MyConsole() =
member this.WriteLine([<ParamArray>] args: Object[]) =
for arg in args do
printfn "%A" arg
let cons = new MyConsole()
cons.WriteLine("abc", 42, 3.14, true)
Events ##
F# classes can have events, and the events can be triggered and responded to.
type MyButton() =
let clickEvent = new Event<_>()
[<CLIEvent>]
member this.OnClick = clickEvent.Publish
member this.TestEvent(arg) =
clickEvent.Trigger(this, arg)
// test
let myButton = new MyButton()
myButton.OnClick.Add(fun (sender, arg) ->
printfn "Click event with arg=%O" arg)
myButton.TestEvent("Hello World!")
Delegates ##
F# can do delegates.
// delegates
type MyDelegate = delegate of int -> int
let f = MyDelegate (fun x -> x * x)
let result = f.Invoke(5)
Enums ##
F# supports CLI enums types, which look similar to the "union" types, but are actually different behind the scenes.
// enums
type Color = | Red=1 | Green=2 | Blue=3
let color1 = Color.Red // simple assignment
let color2:Color = enum 2 // cast from int
// created from parsing a string
let color3 = System.Enum.Parse(typeof<Color>,"Green") :?> Color // :?> is a downcast
[<System.FlagsAttribute>]
type FileAccess = | Read=1 | Write=2 | Execute=4
let fileaccess = FileAccess.Read ||| FileAccess.Write
Working with the standard user interface ##
Finally, F# can work with the WinForms and WPF user interface libraries, just like C#.
Here is a trivial example of opening a form and handling a click event.
open System.Windows.Forms
let form = new Form(Width= 400, Height = 300, Visible = true, Text = "Hello World")
form.TopMost <- true
form.Click.Add (fun args-> printfn "the form was clicked")
form.Show()