In this article we continue the OO leg of our F# journey. So this time we will look at abstract classes / overriding methods and how to implement interfaces
Abstract Methods / Virtual Method
F# like the rest of the .NET languages allow you to mark a method (function) as abstract. There is a subtle difference in that you must declare a member as abstract even you do not supply a default implementation. You can kind of think as abstract methods with no default implementation as a abstract method, and a abstract member with a default implementation as a virtual member. There is no virtual keyword in F#.
In order to have a pure abstract method (that is one with no default implementation) you need to use the special AbstractClass attribute, otherwise you will get a compile time error
Let’s ignore this for now and concentrate on some working code:
Virtual Methods
Here is an example of an abstract method that has a default implementation(which is achieved using the default keyword), which is the equivalent of a virtual method in C#, here is an example of this:
type SomeBaseClass() =
let mutable z = 0
abstract member SomeVirtualMethod : int -> int
default this.SomeVirtualMethod(a : int) =
z <- z + a
z
type SomeDerivedClass() =
inherit SomeBaseClass()
override this.SomeVirtualMethod(a: int) = a * 2
It can be seen that in the SomeDerived class we can override the base implementation by using the override keyword, which works much the same as it does in any other .NET languages, you are selectively choosing to provide a new implementation for the method you are overriding. What is quite different in F# is that you lack some of the finer points of say C#, such as
- Using the “new” keyword to hide an implementation of a particular method
- Calling into the original base class method in an overridden method
Here is an example of a virtual method in use:
let foo = new SomeDerivedClass()
printfn "foo.SomeVirtualMethod(24) %A" (foo.SomeVirtualMethod(24))
Which when run gives this result
Abstract Methods
So we just talked about the F# equivalent of “virtual” members, which were ones that are abstract but also supplied a default implementation. But what about abstract members that do not supply a default implementation, how do we deal with those. Here is a small example of that, this time we have to use the AbstractClass attribute, to denote that the whole class is abstract. This allows the definition of a type with abstract members that do not have any implementation, were the actual implementation will be supplied by the inheritor of the abstract class.
[<AbstractClass>]
type SomeBaseClass() =
abstract member SomeAbstractMethod : int -> unit
type SomeDerivedClass() =
inherit SomeBaseClass()
override this.SomeAbstractMethod(a: int) = printfn "a was %A" a |> ignore
Here is an example of a abstract class/method in use:
let foo = new SomeDerivedClass()
printfn "foo.SomeAbstractMethod(24)" |> ignore
do foo.SomeAbstractMethod(24)
Which when run gives this result
Calling Base Class Constructors
The constructor for the base class must be called in the derived class.The arguments for the base class constructor appear in the argument list in the inherit clause. This is achievable in F# though it is certainly not as pretty as it is in C# say. We have already seen the case when there is a simple no parameter constructor, but what about the cases when the base type contains a constructor that contains parameters or maybe even has several constructors, how do we deal with that. I think the best way to show how to deal with that is by way of an example. So here is an example where we have a base type that has a mixture of constructors, and is then inherited from.
type SomeBaseClass =
val stringField : string
new (s) = { stringField = s }
new () = { stringField = "" }
type DerivedClass =
inherit SomeBaseClass
val stringField2 : string
new (s1, s2) = { inherit SomeBaseClass(s1); stringField2 = s2 }
new (s2) = { inherit SomeBaseClass(); stringField2 = s2 }
new () = { inherit SomeBaseClass(); stringField2 = "" }
As you can see there is little bit more ceremony to deal with here, such as the braces “{}” and the extra use of the inherit keyword wherever you need to call a base class constructor. That said once you get used to it, I don’t think it is that bad.
Object Expressions
Sometimes you may only need to do a minor change, in which case F# offers an alternative approach to inheritance, by way of a technique called “Object Expressions”. Here is a trivial example of this:
let public myObjectExpressionObject =
{
new Object() with
override this.ToString() = "Override the object.ToString() method"
}
It can be seen all we wanted to do was supply a new ToString() method for our usage but we did not inherit from anything at all, in fact there is no custom type at all there, just a let binding and an object expression where we override the ToString() method of the object type.
Which we can use like this:
let result = myObjectExpressionObject.ToString()
printfn "myObjectExpressionObject.ToString() = %A" result
Which will give the expected result of:
Implementing Interfaces / Calling Interface Methods
To define an interface you simply declare a type with abstract members. To implement an interface you simply use the interface XXX with syntax, and then provide the members details for the original members of the interface. Here is a small example.
type IOrderDetails =
abstract member Describe : unit -> string
type Order(x: int, y: string, z: DateTime) =
interface IOrderDetails with
member this.Describe() = String.Format("{0} : {1} x {2}", z.ToShortTimeString(), x, y)
- This declares a very simple interface called IOrderDetails that has a single member
- We then provide a custom Order type that implements the IOrderDetails interface
So that is how you declare an interface, but as some point you will want to be able to call the interface methods that you have implemented, so how do you do that?
If i create a new Order object and look at the intellisense it can be seen that the IOrderDetails.Describe member is not listed there:
mmmmm perhaps some sort of cast is required to cast the Order instance to the IOrderDetails interface that it implements. Yes this is the correct answer. In C# you have 2 choices here, you can use the cast operator ((IOrderDetails)o).Describe() or you may use the as keyword which can be used like this (o as IOrderDetails).Describe(). We are however not using C# and are using F, so we need to focus on what we need to do in F#.
The idea is the same though, we need to cast. Here is the working code for the example IOrderDetails interface implementation:
let o = new Order(1, "Star Wars DVD", DateTime.Now)
printfn "((o :> IOrderDetails).Describe()) = %A" ((o :> IOrderDetails).Describe())
Which when run gives the following (expected) results: