Basics 25 - Classes and Interfaces
Table of contents
- Before We Begin
- Basic Syntax for Creating Classes
- Classes with Additional Constructors and Members
- Basic Syntax for Creating Interfaces
- Class Implementing an Interface
- Record Implementing an Interface
- A Generic Interface and Class Implementation
- Let Bindings in Class for Private Fields and Functions
- Class Properties with Backing Stores
- Class Properties without Backing Stores
- Creating Instances of Classes
Learning-FSharp/Ch25-OOP-Classes/Program.fs
Check out all the comments that are placed in the source file.
The objective of this article is to give you an overview of classes and interfaces in F# through various examples.
Before We Begin
Throughout this series, I have and will continue to speak against OOP. You may wonder why am I covering classes and interfaces. Largely for 3 reasons:
Some features of F# can't be used without classes; the prime example being computation expressions
You need to use existing classes and interfaces that belong to .NET Framework or third-party libraries
You may need to implement areas of a project in F# that should be exposed in C# in OOP fashion
My goal is to give an overall idea of the playing field. I am not covering classes, inheritance and interfaces in depth here. I use these to the absolute minimum possible and stick to functional design.
Basic Syntax for Creating Classes
// The basic template for a class.
// Notice the () after Student1, the type name.
// Student1() is the default constructor for type (class) Student1.
type Student1() =
class
end
A class must have the default constructor
The default constructor is part of the type declaration,
Student1()
in this case
Classes with Additional Constructors and Members
// Class/type Student2.
// The default constructor takes two arguments name and age.
type Student2(name: string, age: int) =
class
// This area is actually default constructor's body
do printfn "Creating Student2 with name: %s" name
do printfn "Creating Student2 with age: %d" age
// After the body of the default constructor comes
// the area for members
// Additional constructors
// Must invoke the default constructor
new() =
printfn "%s" "new() Invoked."
Student2("Unknown", 0)
new(name: string) =
printfn "%s" "new(name) Invoked."
Student2(name, 0)
new(age: int) =
printfn "%s" "new(age) Invoked."
Student2("Unknown", age)
// An instance method.
// Notice name and age are available.
// this is the name used to refer to the current instance.
// You can name it this or anything else.
member this.NamePlusAge() = $"{name} {age}"
// Notice this is replaced with current.
member current.AnotherNamePlusAge() = $"{name} {age}"
// A static method
static member Create name age = Student2(name, age)
end
The default constructor is
Student2(name: string, age: int)
Three additional constructors are created:
new()
,new(name: string)
andnew(age: int)
Notice that all additional constructors must call the default constructor
The area between the beginning of the class body and the area for class members is the body of the default constructor
Instance members:
Unlike C#/Java, you are free to refer to the current/calling instance by any name
member this.NamePlusAge() =
refers to the current instance bythis
member current.AnotherNamePlusAge() =
refers to the current instance bycurrent
Static members are created with
static member
Basic Syntax for Creating Interfaces
// The basic template for an Interface.
type INamePlusAge =
interface
// A method for derived types to implement.
abstract member NamePlusAge: unit -> string
end
Use
abstract member
declaring members in the interfaceThese members must be defined the type implementing the interface
Class Implementing an Interface
// Example of a class implementing an interface.
type Student3(name: string, age: int) =
class
// Default constructor body.
do printfn "%s %d" name age
// Area for members.
// Implementation of interface method.
interface INamePlusAge with
member this.NamePlusAge() = $"{name} {age}"
end
Implement interface with
interface <Interface Name> with
Define all the members that are part of the interface
Record Implementing an Interface
// Example of a record implementing an interface
type StudentRecord =
{ Name: string
Age: int }
interface INamePlusAge with
member this.NamePlusAge() = $"{this.Name} {this.Age}"
- Same as the class
A Generic Interface and Class Implementation
// Here's an interface with generic type T.
type IGenericIntarfec<'T> =
interface
// Methods for derived types to implement.
abstract member Get: unit -> 'T
abstract member GetToString: unit -> string
end
// A class implementing IGenericIntarfec<'T>.
type MyClass<'T>(t: 'T) =
class
interface IGenericIntarfec<'T> with
member this.Get() = t
member this.GetToString() = t.ToString().ToUpper()
end
- Here's an interface with a generic type
'T
Let Bindings in Class for Private Fields and Functions
// Example of let bindings within a class.
// Use let bindings for creating provate fields and functions.
type ClassWithLetBindings(name: string, age: int) =
class
// 2 private fields and a private function.
let _name = name
let _age = age
let agePlus num = _age + num
// A private static field.
static let _a = "A"
// A private static function.
static let _add2Nums x y = x + y
member this.NamePlusAge() = $"{name} {age}"
member this.YetAnotherNamePlusAge() = $"{_name} {_age}"
member this.AgePlusNum num = agePlus num
end
You can use
let
bindings to create private fields and functionsSimilarly, you can use
static let
to create private static fields and private static functions
Class Properties with Backing Stores
// Example of properties with backing stores.
type ClassWithProperties(name: string, age: int, rollNo: string) =
class
let _name = name
let mutable _age = age
let mutable _rollNo = rollNo
// A read-only property.
member this.Name = _name
// A write-only property.
member this.RollNo
with set (value) = _rollNo <- value
// A read-write property.
member this.Age
with get () = _age
and set (value) = _age <- value
end
- Here's an example of properties that are linked with backing stores (fields)
Class Properties without Backing Stores
// Example of properties with no backing stores.
type ClassWithPropertiesWithNoBackingStore(name: string, age: int, rollNo: string) =
class
// A read-only property.
member val Name = name
// 2 read-write properties.
member val Age = age with get, set
member val RollNo = rollNo with get, set
end
- Here's an example of properties without backing stores (fields)
Creating Instances of Classes
// new is optional
let student1_1 = Student1()
let student1_2 = new Student1()
// Invoking difference constructors
let student2_1 = Student2()
let student2_2 = Student2("Name")
let student2_3 = Student2(10)
let student2_4 = Student2("Name", 10)
// Interface pointing to class and record
let iface_1: INamePlusAge = Student3("Name", 10)
let iface_2: INamePlusAge = { Name = "Name"; Age = 10 }
// Calling interface method
let x = iface_1.NamePlusAge()
let y = iface_2.NamePlusAge()
// If you need to call interface methods,
// can't be done directly from class or record
let student3_1 = Student3("Name", 10)
// student3_1.NamePlusAge <- will result in compile error
// Use casting operating :>
let namePlusAge = (student3_1 :> INamePlusAge).NamePlusAge()
// Generic class instance
let g_1 = MyClass<int>(10)
let g_2 = MyClass<string>("Ten")
// Casting to interface
let x = (g_1 :> IGenericIntarfec<int>).Get()
let y = (g_2 :> IGenericIntarfec<string>).Get()
If you have reached so far, congratulations.
Keep reading!