Chapter 2. Using F# for Object-Oriented Programming

Object-oriented programming (OOP) has been a great success. Many modern business systems are designed and implemented by applying OOP concepts. I will not discuss the benefit of using OOP because, as a C# developer, you probably already have hundreds of such benefits in mind.

F# is a multiparadigm language that supports OOP. Practically everything you can do with C# can be done with F#, and usually with less code. I will follow the approach from the first chapter, converting C# code to F#. Example 2-1 shows a C# Point2D class, which represents a point on a two-dimensional (2D) surface. It shows properties, static and nonstatic fields, an attribute, and member methods. The conversion task will not only do the straightforward translation, but also perform some extra work to demonstrate related F# concepts.

Example 2-1. C# Point2D class definition
 // define a 2D point
[DebuggerDisplay("({X}, {Y})")]
public class Point2D
{
    // define a counter
    private static int count = 0;

    // define the original point
    public readonly Point2D OriginalPoint = new Point2D(0, 0);

    // define an X property with explicit back-end field x
    private double x;
    public double X
    {
        get { return this.x; }
        set { this.x = value; }
    }

    // define a Y property
    public double Y { get; set; }

    //length to original point (0,0)
    public double LengthToOriginal
    {
        get { return this.GetDistance(this.OriginalPoint); }
    }

    // default constructor
    public Point2D()
        : this(0,0)
    {}

    // constructor with parameters x and y
    public Point2D(double x, double y)
    {
        this.X = x;
        this.Y = y;
        count++;          // increase the counter when created a new instance
    }

    // static method convert (x,y) to Point2D
    public static Point2D FromXY(double x, double y)
    {
        return new Point2D(x, y);
    }

    // compute the distance to a given point
    public virtual double GetDistance(Point2D point)
    {
        var xDif = this.X - point.X;
        var yDif = this.Y - point.Y;
        var distance = Math.Sqrt(xDif * xDif + yDif * yDif);
        return distance;
    }

    //override the ToString function
    public override string ToString()
    {
        return String.Format("Point2D({0}, {1})", this.X, this.Y);
    }
}

Using Classes

When defining a class, C# always needs the class keyword. In F#, this keyword is optional. Example 2-2 demonstrates a class definition in F#.

Example 2-2. A class definition

Class definition without a default accessibility modifier

// define a public Point2D class
type Point2D() = class

  end

Class definition with an explicit accessibility modifier

// define an internal Point2D class
type internal Point2D() = class

  end

// define a class without the class keyword
type MyClass() =
    // class fields, methods, and properties, such as the following field definition
    let myField = 9

Note

If the class does not contain any member or operation, you have to use class keyword; otherwise, class keyword is optional.

You might be wondering where the public keyword is. Unlike class in C#, which is internal by default, the F# class is public by default. Ignoring the public keyword can save some typing. F# also supports using modules to group code. Table 2-1 lists all of the accessibility modifiers that F# supports.

Table 2-1. Accessibility modifier definitions

Accessibility Modifier

Description

public

The public modifier indicates that the type, value, and function are accessible anywhere.

private

The private access modifier is the least-permissive access level. Private members are accessible only within the body of the class or the struct in which they are declared. The class field is private by default.

internal

The internal modifier is same as public if there is only one assembly. The internal types are not accessible from another assembly. It is public when accessed from the same assembly and private when accessed from a different assembly.

Note

F# does not support the protected keyword. F# honors protected visibility when the type is defined in a language that supports protected-level visibility. F# intends to use less protected level members in a class than in C#; instead, it uses interfaces, object expressions, and higher order functions.

In Example 1-69, we introduced the mutually recursive function definition. You can use the same approach to define mutually recursive types. In Example 2-3, we define a mutually recursive type.

Example 2-3. Mutually recursive type definition without using the class keyword
type A() =
    let b = B()
and B() =
    let a = A()

Unlike C#, F# does not support partial classes, so you have to put all the class code in a single file. F# provides a type-extension feature, which I’ll introduce later in this chapter, to provide similar functionality.

Adding Fields

We need to put something in the empty class definition, and a logical place to start is by adding a field. The way F# defines a class field is not much different than defining a value in a module. Example 2-4 demonstrates how to define a field, named x, in the Point2D class. Once the class is not empty, we can ignore the class...end keywords. The initial value for x is set to 0.0, and its type is inferred to an F# float type.

Example 2-4. Defining field x in class Point2D
// define a Point2D class
type Point2D() =
    // define field x
    let mutable x = 0.0

Note

The let keyword can define only a private field or function when used in a class.

Another way to define an explicit field is to use the val keyword. Example 2-5 defines an explicit field x using the val keyword. When an explicit field is defined, the type information must be provided explicitly in the field definition. The explicit field definition can have accessibility modifiers such as public. The field named y in Example 2-5 is a public field.

Example 2-5. Defining an explicit field in a class
// define a Point2D class
type Point2D() =
    // define a field x using explicit field
    [<DefaultValue>] val mutable x : float

    // define a field y with public accessibility modifier
    [<DefaultValue>] val mutable public y : float

Note

The DefaultValue attribute is mandatory if the class has a primary constructor. I will explain this later in the Using Constructors section.

There is no way to define a public static field.

The DefaultValue attribute specifies that fields x and y are initialized to zero because both fields support zero-initialization. We can say that the type supports zero-initialization if the type meets one of the following conditions:

  • A primitive type that has a zero value, such as integer.

  • A type that supports a null value, either as a normal value, as an abnormal value, or as a representation of a value, such as a class.

  • A .NET value type, such as integer or DateTime. The full list is available at http://msdn.microsoft.com/en-us/library/hfa3fa08(v=vs.110).aspx.

  • A structure and all its fields support a default zero value.

Like C#, there are several ways to represent any one thing. Another way to set the default value is to use the Unchecked.defaultof operator. The function takes a type parameter and returns the default value for that type. It is like the default(T) operator in C#. Example 2-6 shows a sample of how to define a default value.

Example 2-6. Using Unchecked.default to set a default value
// define a Point2D class
type Point2D() =
    // define int field x with Unchecked.defaultof operator
    let mutable x = Unchecked.defaultof<int>

Defining a Property

Next we will define a property using the member keyword. The property name is always prefixed with a self-identifier. The code in Example 2-7 also uses this keyword as a self-identifier. F# supports user-defined self-identifiers, so you can use any valid identifier name as a self-identifier. This feature can improve the program readability. The self-identifier feature will be discussed later in this chapter. In C#, we can specify a different accessibility level for a property’s getter and setter functions. We can also do this in F# by adding private, public, or internal before the set or get. Example 2-7 shows a sample of a property definition.

Example 2-7. Defining a property

Defining a property with the same accessibility modifier

// define a Point2D class
type Point2D() =
    // define field x
    let mutable x = 0.0

    // define X property
    member this.X with get() = x
                  and set(v) = x <- v

Defining a property with a different accessibility modifier

// define a Point2D class
type Point2D() =
    // define field x
    let mutable x = 0.0

    // define an X property
    member this.X with get() = x
                  and private set(v) = x <- v

C# has a shortcut definition for a property. In C# 3.0 and later, the auto-implemented property was introduced. The property Y uses the auto-implemented property syntax. F# 3.0 also has this feature, as shown in Example 2-8. The 0 in the line member val Y = 0 with get, set is the initial value for the Y property, and this initial value also is responsible for indicating the variable type. When the initial value is 0, the value type is automatically inferred to be integer. The F# compiler generates a back-end field Y@, and it is decorated with the CompilerGenerateAttribute attribute.

Example 2-8. An auto-implemented property
// define a Point2D class
type Point2D() =
    // define field x
    let mutable x = 0.0

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = 0.0 with get, set

Note

The auto-implemented property does not support getters or setters with different levels of accessibility. As a result, you cannot define a private setter function when using an auto-implemented property.

An indexed property is nothing but a property with some input parameters. Example 2-9 shows a sample of an indexed property. It defines a Screen type and is used to retrieve the point by using an index. The Item property is a special property that can be used in two ways: screen.[0] or screen.Item(0).

Example 2-9. An indexed property
 // define a Screen class with a point list
type Screen() =
    let points = System.Collections.Generic.List<Point2D>()

    // the item property
    member this.Item with get(x) = points.[x]
                     and set x v = points.[x] <- v

Note

The setter function does not take an (x, v) tuple. It has two parameters, x and v.

C# provides an object-initializer feature for setting property values. F# has a similar feature that can be used to initialize property values. Example 2-10 provides an example. In this example, p is initialized with x and y set to 1.

Example 2-10. Setting a property value when creating the object
type Point2D() =
    member val X = 0. with get, set
    member val Y = 0. with get, set

let p = Point2D(X = 1., Y = 1.)

Defining a Method

A method is a function associated with a type. It can be called by a member method or a member function. If you can define a property, you will not have any problem defining a method. In Example 2-11, we define a GetDistance method. The GetDistance method definition needs to tell the compiler that the input parameter point is a Point2D type. Type inference doesn’t work in this case, we first access the property. The C# version of this method is marked as virtual, but the code in Example 2-11 is not a virtual method. I will discuss how to define a virtual method in a later section.

Example 2-11. Defining a member function
// define a Point2D class
type Point2D() =
    // define field x
    let mutable x = 0.0

    // define X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define Y property using an auto-implemented property
    member val Y = 0.0 with get, set

    // define a function to get the distance from the current point
    member this.GetDistance (point:Point2D) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

Note

The double star (**) operator is used to compute the exponential value of a given number, and the sqrt function is equivalent to Math.Sqrt.

Shipped with Microsoft Visual Studio 2010, C# introduced optional parameters and named parameter features. These two types of features are also available in F#. Example 2-12 shows a C# version of GetDistance with optional parameters defined and its F# equivalent. F# does not allow using default values after the parameter name; instead, it uses the defaultArg function. The defaultArg function takes the optional parameter as the first argument and the default value as a fallback value.

Example 2-12. Using optional parameters

C# GetDistance with an optional parameter

// compute the distance to a given point
public double GetDistance(double x = 0, double y = 0)
{
    var xDif = this.X - x;
    var yDif = this.Y - y;
    var distance = Math.Sqrt(xDif * xDif + yDif * yDif);
    return distance;
}

F# GetDistance with an optional parameter

// define a function to get the distance from the current point
member this.GetDistance(?xValue, ?yValue) =
    // take the (xValue, yValue) tuple and decide whether to use the default value
    let x, y = (defaultArg xValue 0., defaultArg yValue 0.)

    let xDif = this.X - x
    let yDif = this.Y - y
    let distance = sqrt (xDif**2. + yDif**2.)
    distance

When the optional parameter is exposed to a C# project, the C# project needs to reference Microsoft.FSharp.Core.dll, which you can use to create the F# option types. See Example 2-13.

Example 2-13. Referencing the F# method with an optional parameter

F# code defining a method with an optional parameter

namespace MyClassNamespace

type MyClass() =
    member this.PrintValue(?value:int) =
        match value with
            | Some(n) -> printfn "value is %A" n
            | None -> printfn "sorry, no value"

C# code invoking an F# method with an optional parameter

static void Main(string[] args)
{
    var myValue = new MyClassNamespace.MyClass();

    myValue.PrintValue(Microsoft.FSharp.Core.FSharpOption<int>.None);
    myValue.PrintValue(new Microsoft.FSharp.Core.FSharpOption<int>(1));
}

Execution result from the C# code

sorry, no value
value is 1

If adding a reference to FSharp.Core.dll is not the preferred way of doing this, you can use the System.Runtime.InteropServices.DefaultParameterValueAttribute to expose the optional parameter. Example 2-14 shows a sample.

Example 2-14. Exposing an F# optional parameter without reference to FSharp.Core.dll

F# code exposing an optional parameter

namespace MyClassNamespace

open System.Runtime.InteropServices

type MyClass() =
    member this.PrintValue(?value:int) =
        match value with
            | Some(n) -> printfn "value is %A" n
            | None -> printfn "sorry, no value"

    member this.PrintValue2([<Optional;DefaultParameterValue(0)>]value:int,
                            [<Optional;DefaultParameterValue(null)>]str:string) =
        let defaultStr = if str = null then "null value" else str
        printfn "(%A, %A)" value defaultStr

C# code invoking the method with an optional parameter

static void Main(string[] args)
{
    var myValue = new MyClassNamespace.MyClass();

    myValue.PrintValue2(2);
    myValue.PrintValue2();
    myValue.PrintValue2(3, "three");
}

Execution result from the C# code

(2, "null value")
(0, "null value")
(3, "three")

Note

The code segment [<Optional;DefaultParameterValue(0)>] contains two attributes. One is OptionalAttribute, and the other is DefaultParameterValue.

It’s easy to invoke a C# method with an optional parameter from the F# side. Example 2-15 shows how to invoke a C# method with an optional parameter.

Example 2-15. Invoking a C# method with an optional parameter from F#

C# code defining a method with an optional parameter

public class CSharpClass
{
    public void MyMethod(int a = 11)
    {
        Console.WriteLine("a = {0}", a);
    }
}

F# code invoking a C# method with an optional parameter

let c = ConsoleApplication1.CSharpClass()

c.MyMethod()
c.MyMethod(100)
c.MyMethod(a=199)

Execution result from F# code

a = 11
a = 100
a = 199

Defining a Static Method

We have converted the field, property, and member methods from C# to F#. We can now convert the static method FromXY. As in C#, the static keyword is needed to define a static method. One approach to accomplishing this is to use a tuple, and another is to define two independent parameters. If you recall the design principle mentioned in the “Defining Tuples” section in Chapter 1, the tuple approach is a better choice because it requires that both x and y be passed together. The F# code is shown in Example 2-16.

Example 2-16. Defining a static method
// define a Point2D class
type Point2D() =
    // define field x
    let mutable x = 0.0

    // define the X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define the Y property using an auto-implemented property
    member val Y = 0.0 with get, set

    // define a function to get the distance from the current point
    member this.GetDistance (point:Point2D) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x:double, y:double) =
        let point = Point2D()
        point.X <- x
        point.Y <- y
        point

Using Constructors

As a C# developer, you might be thinking that a constructor is nothing more than another function. If so, be prepared for something different. F# has two different kinds of constructors. One type is the primary constructor, whose parameters appear in parentheses just after the type name. Whether or not you are surprised, realize that the set of parentheses after the type name is the parameter to the primary constructor! Another kind of constructor is an optional constructor, which starts with the new keyword. Any such additional constructors must call the primary constructor.

That is a lot of information. Let’s use the code in Example 2-16 as a sample. F# treats all the code between the class name and first class member as initialization code. In the sample code, the let mutable x = 0.0 line of code is initialized when the class is created. If you put a printf statement after let and before any member function, you will see that the message is printed out.

There are two constructors in the C# code. The one that takes two parameters is more general. Please note that primary constructor is always being called first. In the sample, you use the two-parameter constructor as the primary constructor. The code is shown in Example 2-17. The fields x and y take xValue and yValue, respectively, as their initial value. The code in Example 2-18 defines an additional parameterless constructor, which calls into the primary constructor and passes an initial value of 0. Note that F# does not generate a default constructor as C# does. If a class is defined without a constructor, it can be compiled, but there is no way to instantiate it.

Example 2-17. Using a parameterized constructor as the primary constructor
// define a Point2D class with a parameterized primary constructor
type Point2D(xValue:double, yValue:double) =
    // define field x
    let mutable x = xValue

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // define a function to get the distance from the current point
    member this.GetDistance (point:Point2D) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x, y) =
        Point2D(x,y)

Note

The member function cannot be invoked in the constructor code.

In some cases, you need a different way to initialize an object. C# allows multiple constructors. The sample in Example 2-18 shows how to define an additional constructor in F#.

Example 2-18. An additional constructor
// define a Point2D class with a parameterized primary constructor
type Point2D(xValue:double, yValue:double) =
    // define field x
    let mutable x = xValue

    //additional constructor
    new() = Point2D(0.,0.)

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // define a function to get the distance from the current point
    member this.GetDistance (point:Point2D) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x, y) =
        Point2D(x,y)

Although the constructor definition (xValue:double, yValue:double) is right after the type name, you can still squeeze in the accessibility modifier public, internal, or private. Example 2-19 defines a private parameterless constructor and an internal parameterized primary constructor. The internal keyword before Point2D is for the class and the second internal after Point2D is for the primary constructor

Example 2-19. Using accessibility modifiers on constructors

Point2D definition with an accessibility modifier

// define a Point2D class with a parameterized primary constructor
type internal Point2D internal (xValue:double, yValue:double) =
    // define field x
    let mutable x = xValue

    //additional constructor
    private new() = Point2D(0.,0.)

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // define a function to get the distance from the current point
    member this.GetDistance (point:Point2D) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x, y) =
        Point2D(x,y)

Invoke the Point2D

// let internal p = Point2D(X = 1., Y = 1.)
//won't compile because private constructor
let internal p = Point2D(1., 1.)

Let’s stop for a moment to clean up some basic concepts. The code let mutable x = 0.0 in Example 2-16 is called a let binding. The variable defined in a let binding can be accessed at the class level. Actually, the let binding creates a private field or private function. You can think of the let binding as a private function library for the class. As long as a static keyword is present in the definition, a let binding can be a static let binding. The let binding is executed when the instance is created and the static let binding code will be executed before the type is first used.

The let binding can be used in a primary constructor to set an initial value. But what if you want to perform some operations? You can use a do binding during the object-creation phase to perform some extra operations. Like the let binding, the do binding also supports static binding. The nonstatic do binding is executed when the instance is created and the static let binding code will be executed before the type is first used. Example 2-20 demonstrates how to use a let binding and a do binding to implement the count feature that was shown in the C# code.

Example 2-20. An example of let binding and do binding
// define a Point2D class with a parameterized primary constructor
type Point2D(xValue:double, yValue:double) =
    // define field x
    let mutable x = xValue

    // define a static field named count
    static let mutable count = 0

    // perform a do binding to increase the count each time a new instance is created
    do
        count <- count + 1

    //additional constructor
    new() = Point2D(0.,0.)

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // define a function to get the distance from the current point
    member this.GetDistance (point:Point2D) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x, y) =
        Point2D(x,y)

Note

A let binding and do binding cannot have attributes or an accessibility modifier in class definition. A let binding in module can have attribute and accessibility modifiers.

The primary constructor can use a do binding to perform additional initialization code. The then keyword can be used to help a nonprimary constructor execute additional code. See Example 2-21.

Example 2-21. Using the then keyword with a nonprimary constructor
type Student(nameIn : string, idIn : int) =
    let mutable name = nameIn
    let mutable id = idIn

    do printfn "Created a student object"

    member this.Name with get() = name and set(v) = name <- v
    member this.ID with get() = id and set(v) = id <- v

    new() =
        Student("Invalid studnet name", -1)
        then
            printfn "Created an invalid student object. Please input student name."

F# strictly follows a “define first and reference later” approach. As an example, auto-implemented properties insert a back-end field after the do binding. Because of the “define first and reference later” principle, this back-end field is inaccessible in the do binding section of the code. Because of this hidden field insertion, the code in Example 2-22 is invalid. When you perform the do binding, the back-end Y@ field has not been initialized, so the code generates an InvalidOperationException error. Because of this, we will not use an auto-implemented property; instead, we’ll stick to the old way of defining a property. You can still use an auto-implemented property if the constructor logic is simple.

Example 2-22. An auto-implemented property that affects do binding

The following code won’t compile

// An auto-implemented property that causes the following code to not compile
type A() as this =
    do this.F()
    // let Y@ = 0    //hidden backend field
    member this.F() = printfn "aaa"  // InvalidOperationException error!
    member val Y = 0 with get,set

let a = A()

There’s one more topic I want to cover before I finish talking about F# constructors: static constructors. Example 2-23 shows how to define a static constructor code and its execution result.

Example 2-23. Static let/do code
// define a Point2D class with a parameterized primary constructor
type Point2D(xValue:double, yValue:double) =
    // define a static field named count
    static let mutable count = 0
    static do printfn "point2D constructor"

// define a screen class
type Screen() =
    let points = System.Collections.Generic.List<Point2D>()
    static do printfn "screen constructor"
    member this.ShowPoint() = printf "inside ShowPoint"

// define a test module
module TestModule =
    let screen = Screen()
    screen.ShowPoint()

Execution result

point2D constructor
screen constructor
inside ShowPoint

Note

XML document for primary constructor is not supported. The workaround is to define another constructor with a dummy parameter, as in this example:

/// my xml doc
type A(args) =
                <your code>

// change to the following code with a dummy parameter
type A private(args, _dummyParameter:unit) =
                /// docs
                new(args) = A(args, ())

Creating an Indexer

If you want to create an indexer in F#, you need to define a special property named Item. The Item property will be evaluated whenever the .[...] is invoked. Example 2-24 demonstrates how to define an indexer with one parameter and an indexer with two parameters.

Example 2-24. An indexer with one parameter and an indexer with two parameters

Defining the indexer

// define a Screen class
type Screen() =
    let points = System.Collections.Generic.List<Point2D>()
    let width = 800
    let height = 600
    // <some code to initialize the points list to 800>
    do
        points.AddRange(Seq.init (width*height) (fun _ ->Point2D()))

    // an indexer that takes one parameter
    member this.Item with get(x:int) = points.[x]
                     and set(x:int) (v) = points.[x] <- v

    // an indexer that takes two parameters
    member this.Item with get(x:int, y:int) = points.[x + width*y]
                     and set(x:int, y:int) v = points.[x + width*y] <- v

Invoking the indexer

let s = Screen()
s.[1,1] <- Point2D()

Using a Self-Identifier

Everything seems to be working perfectly until you try to assign a value to your property in the do binding section in Example 2-24. If you put this.Y <- 0 in the do binding section, the F# compiler spits out an error message complaining that this is not defined. This error can throw most C# developers off their chairs. Why does the keyword need to be defined?

A self-identifier is a reference to the current instance. The this in C# and Me in Microsoft Visual Basic are self-identifiers. Interestingly, F# does not require that the self-identifier be any particular word. You can use any word as the self-identifier for the whole class definition or just for an individual method:

  • To define a self-identifier for the whole class, use the as keyword after the closing parentheses of the primary constructor and specify the identifier name.

  • To define a self-identifier for just one method, provide the self-identifier in the member declaration before the method name and use a period (.) as a separator in between.

Example 2-25 shows these two ways of creating a self-identifier. The scope for self is established for the whole class and the self-identifier is only in the Print method. So Print is prefixed with this, and you can still use self to invoke Print in the default constructor. Keep in mind that the self-identifier is decorated on the primary constructor. It is required to have a self-identifier on the other constructors if the constructor code needs to access the resource from the current class instance.

Example 2-25. Using a self-identifier
type MyClass(v) as self =
   let data = v
   do
       self.Print()
   member this.Print() =
       printf "MyClass print"

Now the error about this not being defined is explained. To get the property working in the do binding section, you have to define the self-identifier at the class level. See Example 2-26 for the solution.

Example 2-26. The self-identifier and access property in the do binding section
// define a Point2D class with a parameterized primary constructor
type Point2D(xValue:double, yValue:double) as this=
    // define field x
    let mutable x = xValue

    // define a static field named count
    static let mutable count = 0

    // perform a do binding to increase the count
    do
        count <- count + 1
        this.Print("in constructor")

    //additional constructor
    new() = Point2D(0.,0.)

    // print out message
    member this.Print(msg) = printfn "%s" msg

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // define a function to get the distance from the current point
    member this.GetDistance (point:Point2D) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x, y) =
        Point2D(x,y)

The C# class has its ToString method overridden. To do this in F#, you can use the override keyword. Example 2-27 shows how to override the ToString function in F#. Inside the ToString implementation, you use sprintf as an alternative to String.Format. The sprintf function can be viewed as a strongly typed version of String.Format. You pretty much have the conversion work done, but let’s continue using Point2D as a sample to explore more OOP concepts in F#.

Example 2-27. Function override
// define a Point2D class with a parameterized primary constructor
type Point2D(xValue:double, yValue:double) as this=
    // define field x
    let mutable x = xValue

    // define a static field named count
    static let mutable count = 0

    // perform a do binding to increase the count
    do
        count <- count + 1
        this.Y <- 0.

    //additional constructor
    new() = Point2D(0.,0.)

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // define a function to get the distance from the current point
    member this.GetDistance (point:Point2D) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x, y) =
        Point2D(x,y)

    // override the ToString function
    override this.ToString() =
        // String.Format("Point2D({0}, {1})", this.X, this.Y)
        sprintf "Point2D(%f, %f)" this.X this.Y

Note

In C#, you can use the new modifier to explicitly hide a member inherited from a base class. F# does not need to use the new keyword. F# by default hides members from base class unless the override keyword is used.

Also, sprintf provides the same functionality as String.Format. sprintf requires the pattern string specify the data type, such as %d for integer, and it also supports %O and %A, which accept any data type and they convert values to string as String.Format does. Additionally, sprintfn does not support a pass-pattern string as parameter, such as sprintfn myPattern 1 2.

Another concept in object-oriented programming is inheritance. In a moment, I’ll describe how to implement inheritance in F#.

Using a Special/Reserved Member Name

The variable name in F# can be a keyword and supports space and other special characters. You can use these special strings or characters to define the class member name as well. Example 2-28 shows a sample.

Example 2-28. Using special characters and keywords in a class member definition
let f() =
    let ``else`` = 99
    let ``end`` = 100
    let ``F#`` = 101
    let ``my value`` = 102
    let ``let`` = 103
    ()
f()

type MyClass() = class
    member val ``else`` = 0 with get, set
    member val ``end`` = 0 with get, set
    member val ``F#`` = 0 with get, set
    member val ``My Value`` = 0 with get, set
    member val ``let`` = 0 with get, set
end

let myValue = MyClass()

printfn "put break point here"

Note

The Watch window in Microsoft Visual Studio does not support all these characters and keywords. It does not show C# keywords and special character variable names, but it does support view if these special characters and keywords are class members. The following two screen shots show how it works:

image with no caption
image with no caption

Using Inheritance

F# uses the inherit keyword to represent the inheritance relationship. Example 2-29 shows the C# code and its equivalent F# code.

Example 2-29. Particle class using inheritance

C# Particle class code

public class Particle : Point2D
{
    public double Mass { get; set; }
}

F# code

type Particle() =
    inherit Point2D()
    member val Mass = 0. with get, set

You might not have fully recovered yet from the shock of the F# self-identifier. Does F# allow a user to define any identifier for referring to a base class instance? The answer is no. Like C#, the base keyword is used to refer to the base class instance, and a user cannot define her own identifier.

Example 2-30 shows that the inherit keyword can be used to invoke a base class constructor. When the base class has multiple constructors, the inherit keyword can indicate which constructor to use.

Example 2-30. Using the inherit keyword to invoke different base class constructors
// define BaseClass
type BaseClass =
    val x : int
    new () = BaseClass(0)
    new (a) = { x = a }

// define a derived class inherited from BaseClass
type DerivedClass =
    inherit BaseClass
    val y : int

    // invoke the base constructor with one parameter
    new (a, b) = { inherit BaseClass(a); y = b }

    // invoke the base constructor without a parameter
    new (b) = { inherit BaseClass(); y = b }

If the inherit declaration appears in the primary constructor, it is called implicit class construction. The code in Example 2-31 is a sample. It is always a good practice to use an implicit constructor and provide an explicit constructor (or constructors) to overload. However, the code in Example 2-30 does not include a primary constructor and instead explicitly specifies the desired base class constructor. We refer to this as explicit class construction. It is a recommended way to use implicit constructors.

Example 2-31. Example of a keyword base
type Particle() =
    inherit Point2D()
    member val Mass = 0. with get, set
    override this.ToString() =
        sprintf "Particle_%s with Mass=%f" (base.ToString()) this.Mass

Using Abstract and Sealed Classes

The class hierarchies that are formed from inheritance make a tree structure. Sometimes the root of the tree can be an abstract class. C# uses an abstract keyword to define an abstract class. Unlike C#, F# requires the AbstractClass attribute to be used to define an abstract class when no default implementations are provided. See the code in Example 2-32.

Example 2-32. Defining an abstract class
[<AbstractClass>]
type MyAbstractClass() =
    member this.F() = 1

    // define an abstract member function
    abstract member FunctionNotImplemented : unit -> int

Tip

If you cannot remember how to write the function definition for an abstract member method, you can always use FSI. For example, if you want to define a function with an int parameter as input and an int as output, you can type let f (i:int) = 1;; in FSI and the val f : int -> int will display the function definition, which is int->int.

In C#, a method can be overridden if it is a virtual method. F# does not have a virtual keyword to define a virtual method. Instead, F# uses the abstract keyword and the default keyword to define a virtual method. In Example 2-33, the GetDistance method is defined as a virtual method, and then a default implementation of that method is defined.

Example 2-33. Implementing a virtual function by using abstract and default
// define a Point2D class with a parameterized primary constructor
type Point2D(xValue:double, yValue:double) as this=
    // define field x
    let mutable x = xValue

    // define a static field named count
    static let mutable count = 0

    // perform a do binding to increase the count
    do
        count <- count + 1

    //additional constructor
    new() = Point2D(0.,0.)

    // print out message
    member this.Print() = printfn "In class Point2D"

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // define a virtual function to get the distance from the current point
    abstract GetDistance : point:Point2D -> double
    default this.GetDistance(point) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x, y) =
        Point2D(x,y)

    override this.ToString() = sprintf "Point2D(%f, %f)" this.X this.Y

C# provides the new keyword to provide different implementations for a nonvirtual method. Overriding a nonvirtual method is not a good practice, although it is a necessary approach under certain circumstances. F# does not support the new keyword like C# does, but it still allows users to do it. If you override a nonvirtual method, there will be a warning. Example 2-34 shows a sample.

Example 2-34. Overriding a nonvirtual method

C# code using the new keyword

public class MyClass : List<int>
{
    public new void Add(int n)
    {
        base.Add(n);
        // <additional operations>
    }
}

F# code overriding a nonvirtual method

type MyList () =
    inherit System.Collections.Generic.List<int>()     //inherit List

    //add element
    member this.Add(n) =  // yield a warning 864
        base.Add(n)
        // <additional operations>

Note

You can use #nowarn "864" to disable the warning.

I’m going to change direction slightly and introduce attributes. They are used to define F# features such as an abstract class and sealed classes. An attribute in F# is used in a way that is very similar to C#. Example 2-35 shows how to decorate the Point2D class with DebuggerDisplayAttribute.

Example 2-35. Adding the DebuggerDisplay attribute to a class
// define a Point2D class with a parameterized primary constructor
[<System.Diagnostics.DebuggerDisplay("({X}, {Y})")>]
type Point2D(xValue:double, yValue:double) as this=
    // define field x
    let mutable x = xValue

    // define a static field named count
    static let mutable count = 0

    // perform a do binding to increase the count
    do
        count <- count + 1

    //additional constructor
    new() = Point2D(0.,0.)

    // print out message
    member this.Print() = printfn "In class Point2D"

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

The AbstractClass attribute is used to define an abstract class, and the Sealed attribute is for defining a sealed class. See Example 2-36.

Example 2-36. A Sealed class definition
[<Sealed>]
type MySealedClass() =
    member this.F() = 1

Creating an Instance

F# has the new keyword to create an object. The new keyword is optional when creating a new object unless the class implements the IDisposable interface. If the new keyword is not used, there will be a compile-time warning indicating the new keyword should be used and the class will implement the IDisposable interface. See Example 2-37.

Example 2-37. Using the new keyword when creating an object that implements IDisposable
// create object with the new keyword for a class implementing the IDisposable interface
let myObj = new System.Data.SqlClient.SqlConnection()

// Warning: It is recommended that objects that support the
// IDisposable interface are created using 'new Type(args)'
// rather than 'Type(args)' to indicate that resources may
// be owned by the generated value
let myObj2 = System.Data.SqlClient.SqlConnection()

Note

When the class implements the IDisposable interface, use can replace let in a nonmodule context. Keep in mind that use is treated like let in a module and use in C# is different from in F#.

As a C# developer, you might use the new keyword everywhere through force of habit. If that is the case, you should be aware of one small catch when using the new keyword. When using the new keyword with generic types, the type parameter is automatically set to System.Object in Example 2-38 to narrow the scope of the code shown below. Also, the type parameter cannot be ignored if you use the new keyword, even if your type parameter is System.Object. See Example 2-38.

Example 2-38. Using the new keyword with a generic type
let f ()=
    let myDictionary = System.Collections.Generic.List()
    printfn "my type is %A" (myDictionary.GetType())

// following code does not compile because it needs the type parameter
//    let myDictionary2 = new System.Collections.Generic.List()
//    printfn "my type is %A" (myDictionary.GetType())

    let myDictionary2 = new System.Collections.Generic.List<int>()
    printfn "my type is %A" (myDictionary2.GetType())

f()

Execution result

my type is System.Collections.Generic.List'1[System.Object]
my type is System.Collections.Generic.List'1[System.Int32]

Note

Creating a generic type without specifying the type parameter does not always work at the top level.

Using Type Casting

I’ve already introduced many data types, including both basic types and complex class types. Let me now leave the C# code conversion task and introduce type casting. In a real-world application, you might need to do casting, which could involve not only basic data types, from int to float, but also object casting up and down the class hierarchy.

Converting Numbers and Using enum

Let’s start with a basic type of conversion: number conversion. The way F# converts a number is similar to the way that C# does it. See Example 2-39.

Example 2-39. Converting a number

C# conversion

int i = 5;
double d = (double)i

F# conversion

let i:int = 5
let d:double = double(i)

Note

C# supports implicit conversion, which means that in C# you can assign an integer to a double without having to explicitly do the casting. The ability to deduce type and implicitly convert type can make development easier at earlier stages, but it introduces more chances to generate runtime errors. F# statically resolves the type and minimizes the runtime error by allowing only explicit type conversion.

The F# enum conversion is shown in Example 2-40.

Example 2-40. An enum conversion
type PixelColor =
    | R = 0
    | G = 1
    | B = 2

// convert number to enum
let colorR : PixelColor = enum 0
let colorB = enum<PixelColor> 2

Upcasting and Downcasting

Casting an object up the hierarchy is the act of casting a derived object to its base object. You can use the :> operator or the upcast keyword to convert a derived class instance to a base class instance. Example 2-41 shows a sample of how to use :> and upcast.

Example 2-41. Upcasting conversion
// define a base class
type Base() = class end

// define a derived class
type Derived() = inherit Base()

let myDerived = Derived()

// upcasting
let cast = myDerived :> Base

// use upcast
let cast2 : Base = upcast myDerived

Note

Casting to a base class (by using :> or upcast) is always safe and can be verified at compile time.

Casting an object down the hierarchy happens when you need to cast from a base object to a derived object. The :?> operator or the downcast keyword is used to accomplish this. The downcast operator does some sanity checks; for example, the type casting down needs to have an inheritance relationship with current type, but most of the checking still has to be verified at runtime. If the cast fails, an InvalidCastException will be raised. Example 2-42 casts the base class to a derived class.

Example 2-42. An example of downcasting
// define a base class
type Base() = class end

// define a derived class
type Derived() = inherit Base()

let myDerived = Derived()

// upcasting
let upCastResult = myDerived :> Base

// use upcast
let cast2 : Base = upcast myDerived

// downcasting
let downCastResult = upCastResult :?> Derived

//use downcast
let case3 : Derived = downcast cast

Note

The upcast and downcast keywords are alternatives to the :> and :?> operators.

Boxing and Unboxing

Like C#, F# also supports the concept of boxing and unboxing. When the common language runtime (CLR) boxes a value type, it wraps the value inside of a System.Object and stores it on the managed heap. Unboxing is the opposite process; it extracts the value type from the object. Because F# does not support implicit type conversion, an extra step is needed. See Example 2-43.

Example 2-43. Boxing and unboxing
 let intValue = 4
let o = box(intValue)
// let r = o + 2   //does not compile because o is an obj type
let i : int = unbox(o)
let r = i + 2

Note

F# uses obj as a type abbreviation for System.Object.

Defining an Interface

An interface is just a collection of properties and methods, and its implementation is finished in the class that implements the interface. The interface provides an is-a relationship to the type. Once the type implements the interface, it is basically broadcasting that it can perform the operations described in the interface definition. The typical interface declaration in F# is like the code in Example 2-44. Example 2-45 shows how to define an interface with a property and how the lightweight F# syntax allows the interface...end keywords to be optional.

Example 2-44. Defining an interface
type IInterface =
    // a method that takes int and returns int
    abstract member InterfaceMethod : int -> int


type IInterface2 = interface
    // a method that takes int and returns int
    abstract member InterfaceMethod : int -> int
end
Example 2-45. Defining an interface with properties

Interface definition with interface...end

type I2DLocation = interface
    abstract member X : float with get, set
    abstract member Y : float with get, set
end

Lightweight interface definition

type I2DLocation =
    abstract member X : float with get, set
    abstract member Y : float with get, set

Note

Unlike C#, an F# interface does not support a partial keyword. Therefore, an interface has to be in one file.

F# requires that interfaces be explicitly implemented; as a result, the code needs some extra work. See Example 2-46. Adding an interface seems to not add much value to this code; however, the interface can make code easy to expand and testing frameworks such as Fakes in Visual Studio requires an interface.

Example 2-46. Implementing an interface
// 2D location interface
type I2DLocation =
    abstract member X : float with get, set
    abstract member Y : float with get, set


// define a Point2D class with a parameterized primary constructor
type Point2D(xValue:double, yValue:double) as this=
    // <other Point2D members such as property X and Y>
        // define field x
    let mutable x = xValue

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // implement the I2DLocation interface
    interface I2DLocation with
        member this.X with get() = this.X and set(v) = this.X <- v
        member this.Y with get() = this.Y and set(v) = this.Y <- v

    override this.ToString() = sprintf "Point2D(%f, %f)" this.X this.Y

When looking at the code in Example 2-46, some people will be scared because of a possible stack overflow exception. It seems that the code is calling the get() function of property X inside a get() function. After you run the code, there is no stack overflow. When stepping into the code, you will find that the execution is going into the property defined in the class rather than the property in the interface. The this identifier represents the Point2D type, which can be proved by positioning your pointer over the this identify—as you can see in Figure 2-1—so there won’t be a stack overflow exception. If you really want to see a stack overflow, cast this to I2DLocation by using this :> I2DLocation.

The code in Example 2-47 and Example 2-48 shows how to access class methods and how to use a type casting operator to access interface methods.

Example 2-47. Accessing the class property without type conversion
// invoke the Point2D defined in Listing 2-46

let c = Point2D()
let x = c.X
Example 2-48. Accessing the property defined in the interface
let c = Point2D()

//cast c to I2DLocation type
let c2 = c :> I2DLocation

let x = c2.X
Self-identifier in the interface implementation code
Figure 2-1. Self-identifier in the interface implementation code

The explicit interface implementation does introduce some extra work for a developer; however, it provides a clear vision so that you can make sure that the right function can be invoked. For example, there are two interfaces that have defined the same property, and the implementation of one of those properties was accidently forgotten. The discovery of this kind of bug will most likely be delayed to runtime.

Interfaces in F# also support inheritance. The next task is to define a new interface, inherit from I2DLocation, and add an extra method called GetDistance. Example 2-49 shows the sample code.

Example 2-49. Defining an interface and inheritance
// define a base interface
type I2DLocation =
    abstract member X : float with get, set
    abstract member Y : float with get, set

// define an interface that inherits from I2DLocation
type IPoint2D =
    inherit I2DLocation
    abstract member GetDistance : Point2D -> float

Compared with the code in Example 2-49, the code in the Example 2-50 reverses the order of IPoint2D and I2DLocation. This is not a problem for C#, but in F# you have to use the and keyword to resolve the type reference problem. The IPoint2D interface has a function that takes Point2D as a parameter, while the Point2D interface is defined next. Because F# resolves the type and symbols from top to bottom and from left to right, there will be a compile error unless the and keyword is applied.

Example 2-50. Implementing interfaces with an inheritance relationship
// define an IPoint2D interface
// need the and keyword to resolve the type-resolution problem
type IPoint2D =
    inherit I2DLocation

    // this function takes I2DLocation as a parameter, which is defined below
    abstract member GetDistance : I2DLocation -> float

// define a 2D location interface
and I2DLocation =     abstract member X : float with get, set
    abstract member Y : float with get, set



// define a Point2D class with a parameterized primary constructor
type Point2D(xValue:double, yValue:double) as this=
    // define field x
    let mutable x = xValue

    // define a static field named count
    static let mutable count = 0

    // perform a do binding to increase the count
    do
        count <- count + 1


    //additional constructor
    new() = Point2D(0.,0.)


    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // define a function to get the distance from the current point
    abstract GetDistance : point: I2DLocation -> double
    default this.GetDistance(point) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x, y) =
        Point2D(x,y)

    interface I2DLocation with
        member this.X with get() = this.X and set(v) = this.X <- v
        member this.Y with get() = this.Y and set(v) = this.Y <- v
    interface IPoint2D with
        member this.GetDistance(p) = this.GetDistance(p)

    override this.ToString() = sprintf "Point2D(%f, %f)" this.X this.Y

Using the IDisposable Interface

The IDisposable interface is used mainly for releasing unmanaged resources. The garbage collector can automatically reclaim the memory for managed objects. However, garbage collection lacks key knowledge about the unmanaged resources, such as opened files or streams. By invoking the Dispose method, you have control over when to release the unmanaged resources.

In C#, you have the using keyword to implicitly invoke the dispose method when exiting the code scope. F# also provides the same functionality with the use keyword and using function. Example 2-51 shows the Database class implementing the IDisposable interface, as well as how to instantiate the class by applying the use keyword and the using function. There is only a slight difference between the functionality of use and using: the determination of when the Dispose method is going to be invoked. The use keyword invokes the Dispose function at the end of the containing code block, and the using function calls the Dispose method at the end of the lambda. In the sample code, the Dispose method is invoked when testIDisposable exits, and the using function invokes the Dispose method when testUsing exits. In general, you should choose use over using.

Example 2-51. Using the IDisposable interface

Define a class with the IDisposable interface

type Database(conString) =
    let con = new SqlConnection(conString)
    member this.ConnectionString = conString
    member this.Connect() =
        con.Open()
    member this.Close() = con.Close()
    interface IDisposable with
        member this.Dispose() = this.Close()

Instantiate an object with the use keyword

let testIDisposable() =
    use db = new Database("my connection String")
    db.Connect()

The using keyword used for the IDisposable interface

let testUsing(db:Database) = db.Connect()
using (new Database("my connection string")) testUsing

Note

The use keyword cannot be put into a module directly because it will be treated as a let binding. The using function can be used in a module.

Using F# Generic Types and Constraints

Although F# can provide very good generalized code, it still needs generic types and constraints. Table 2-2 lists C# and F# constraints. From the table, you can see that F# provides more choices.

Table 2-2. C# and F# constraints

C# Constraint

F# Constraint

Description

where T: struct

when ‘T : struct

The type must be a value type.

where T : class

when ‘T : not struct

The provided type must be a reference type.

where T : new()

when ‘T : ( new : unit -> ‘a )

C# constraints requires the type argument to have a public parameterless constructor and the new() constraint must be specified last if there is another constraint. F# requires that the provided type to have a default constructor.

where T : <base type name>

when ‘T :> type

The type argument must be derived from the specified base type. The base type can be an interface.

where T : U

Not supported

The type argument supplied for T must be derived from the argument supplied for U.

Not supported

when ‘T : null

The type needs to support the NULL value.

Not supported

when ‘T or ‘U : (member-signature )

At least one of the type arguments should have a member that has the specified signature.

Not supported

when ‘T : enum<underlying-type>

The provided type must be an enum type that has the specified underlying type.

Not supported

when ‘T : delegate<tuple-parameter-type,return-type>

The provided type must be a delegate type that has the specified arguments and return value.

Not supported

when ‘T : comparison

The provided type must support comparison.

Not supported

when ‘T: equality

The provided type must support equality.

Not supported

when ‘T : unmanaged

The provided type must be an unmanaged type. Unmanaged types are either certain primitive types (such as sbyte, byte, char, nativeint, unativeint, float32, float, int16, uint16, int32, uint32, int64, uint64, or decimal), enumeration types, nativeptr<_>, or a nongeneric structure whose fields are all unmanaged types.

Note

Unlike in C#, new : unit -> a does not need to be the last constraint.

F# handles the NULL value in a different manner, which will be discussed later. Therefore, the F# list, F# tuple, function, F# class, union type, and record type cannot be NULL. I will introduce the union and record types later. Example 2-52 is the sample code that shows how NULL constraints are used. The general rule is if the type is from the Microsoft .NET Framework, the type always supports NULL. F# types do not support NULL unless otherwise specified.

Example 2-52. NULL constraints on some F# types
// generic type with NULL constraint
type MyClass<'T when 'T : null> () =
    let a:'T = Unchecked.defaultof<'T>

// string array that can have NULL as a proper value
let b = MyClass<string []>()

// string sequence that can have NULL as a proper value
let b3 = MyClass<string seq>()

// string list that CANNOT have NULL as a proper value
// let b2 = MyClass<string list>()

type MyRecord = { X : int; Y : string }
// record that CANNOT have NULL as a proper value
// let b4 = MyClass<MyRecord>()

// use .NET tuple as a constraint that can take NULL
let b5 = MyClass< System.Tuple<string,string> >()

// string*string tuple CANNOT have NULL
// let b6 = MyClass< string*string > ()

// function int->int CANNOT have NULL
// let b7 = MyClass< int->int > ()

type MyClass2() = class

  end

// class MyClass2 cannot be NULL
//let b8 = MyClass< MyClass2 >()

Example 2-53 shows how to change the Point2D code to use generic constraints. Example 2-54 shows how to define two type parameters in the generic type definition and how to use the and keyword to chain multiple constraints.

Example 2-53. Generic constraints
// define a 2D location interface
type I2DLocation =
    abstract member X : float with get, set
    abstract member Y : float with get, set

// define a IPoint2D interface
and IPoint2D =
    inherit I2DLocation
    abstract member GetDistance<'T when 'T :> I2DLocation> : I2DLocation -> float

// define a Point2D class with a parameterized primary constructor
type Point2D(xValue:double, yValue:double) =
    // define field x
    let mutable x = xValue

    // define a static field named count
    static let mutable count = 0

    // perform a do binding to increase the count
    do
        count <- count + 1

    //additional constructor
    new() = Point2D(0.,0.)

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // define a function to get the distance from the current point
    abstract GetDistance<'T when 'T :> I2DLocation > : point:'T -> double
    default this.GetDistance<'T when 'T :> I2DLocation >(point:'T) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x, y) =
        Point2D(x,y)

    interface I2DLocation with
        member this.X with get() = this.X and set(v) = this.X <- v
        member this.Y with get() = this.Y and set(v) = this.Y <- v
    interface IPoint2D with
        member this.GetDistance<'T when 'T :> I2DLocation > (p) = this.GetDistance(p)

    override this.ToString() = sprintf "Point2D(%f, %f)" this.X this.Y

Note

If you position your pointer over the definition of GetDistance, it shows override Point2D.GetDistance : point #IPoint2D -> float. The # indicates that the function accepts the object derived from IPoint2D. This is called a flexible type. You can use the “#<type name>” in a class member definition to save yourself some typing.

Example 2-54. Two type parameters with constraints
// two type parameters with constraints
type MyGenericType2<'T when 'T : (new : unit -> 'T) and 'T :> Point2D > () = class
    end

// two type parameters with constraints
type MyGenericType3<'T, 'U when 'T :> Point2D and 'U :> Point2D> () = class
    end

Defining Structure

You might be wondering if a class is really needed when designing such a simple data structure. It is a very good question. For simple data structures like Point, a struct is actually a better choice. A struct is a value type, which means that it can be put on the stack. Struct instances, if allocated on the stack, consume less memory and do not need garbage collection. So let’s go ahead and define a Point2D struct, as shown in Example 2-55.

Example 2-55. Defining a struct for Point2D

Struct definition with struct...end

type Point2D_Struct(xValue:double, yValue:double) = struct
    member this.X = xValue
    member this.Y = yValue
end

Struct definition with a struct attribute

[<Struct>]
type Point2D_Struct(xValue:double, yValue:double) =
    member this.X = xValue
    member this.Y = yValue

There are some restrictions for struct types, though. The struct is, by default, sealed with a [<Sealed>] attribute. It is impossible to override the default constructor, which always exists. All fields in the struct are set to their default values. In Example 2-56, the parameterless constructor is always invoked even if it is not explicitly declared. Another restriction is that a struct cannot use a let binding.

Example 2-56. Creating a struct instance in FSI
> let a = Point2D_Struct()
a.X;;

val a : Point2D_Struct = FSI_0003+Point2D_Struct
val it : double = 0.0

The val binding can be used to define a mutable field, as shown in Example 2-57.

Example 2-57. Defining mutable fields in a struct
[<Struct>]
type Point2D_Struct =
    val mutable X : double
    val mutable Y : double

The struct can have a StructLayout attribute, which lets you control the physical layout of the struct data fields. However, when you apply the attribute, a “warning 9” message is generated:

Uses of this construct may result in the generation of unverifiable .NET IL code. This warning can be disabled using ‘--nowarn:9’ or ‘#nowarn “9”’.

This message warns you of the side effect shown in Example 2-58. Even if the struct is initialized with 1 and 2, fields X and Y are both set to 2. This side effect leads to subtle bugs, and this is why F# chooses to generate a warning.

Example 2-58. Struct with StructLayoutAttribute
[< StructLayout(LayoutKind.Explicit) >]
type D =
    [< FieldOffset 0 >] val mutable X : int
    [< FieldOffset 0 >] val mutable Y : int
    new(a,b) = { X = a; Y = b }

let d = D(1, 2)  // two fields X and Y are both 2

Using Extension Methods

C# introduces a very useful feature called extension methods, which allow for the easy extension of a class without requiring modification of the existing class. When using the type, these extension methods appear to be additional members on the existing type. F# actually provides the same functionality. Example 2-59 shows how F# can extend the built-in system integer type.

Example 2-59. Extension method to the integer type
// extend built-in system type
type System.Int32 with
    member this.ToBinaryString() = sprintf "0x%X" this

Note

The type extension needs to reside in a module. It is invalid to define a type extension in a namespace.

F# can extend built-in system types and customized types. Example 2-60 shows how to extend a user-defined class. The basic syntax is similar to the previous sample. F# extension methods have the same restrictions as C# extension methods. They cannot define a field. Also, they cannot access any fields defined in the main class. In the sample code, it is not possible to access fileName or myField.

Example 2-60. Extending your own types
type File(fileName:string) =
    let myField = 0
    member this.MyFunction() = myField
    member this.FileName with get() = fileName

// extend a user-defined class
type File with
    // let myField2 = 0    //cannot define new field
    member this.GetFileName() = this.FileName

The preceding extension method can be recognized only within the F# world. If you want to expose the extension methods to a C# project, you need to use the System.Runtime.CompilerServices.Extension attribute, as shown in Example 2-61. You have to put the attribute on both the method name and module.

Example 2-61. Extension method for a C# project
[<System.Runtime.CompilerServices.Extension>]
module MyExtensionMethods =

    // extension methods can be invoked from a C# project
    [<System.Runtime.CompilerServices.Extension>]
    let ToBinaryString i = sprintf "0x%X" i

    // F# way to extend a type
    type System.Int32 with
        member this.ToBinaryString() = ToBinaryString this

Note

If the code will be invoked by a C# project, always consider putting the code inside a namespace.

Using Operator Overloading

Operator overloading is used to add new meaning to an operator. The operator that needs to be overloaded has to be in parentheses. If the operator is a unary operator, you have to add a tilde (~) as a prefix. Example 2-62 shows how to add an operator overload in the Point2D class.

Example 2-62. Operator overloading

Defining an operator in a class

// define a 2D location interface
type I2DLocation =
    abstract member X : float with get, set
    abstract member Y : float with get, set

// define a IPoint2D interface
type IPoint2D =
    inherit I2DLocation
    abstract member GetDistance<'T when 'T :> IPoint2D> : IPoint2D -> float

// define a Point2D class with a parameterized primary constructor
and Point2D(xValue:double, yValue:double) =
    // define field x
    let mutable x = xValue

    // define a static field named count
    static let mutable count = 0

    // perform a do binding to increase the count
    do
        count <- count + 1

    //additional constructor
    new() = Point2D(0.,0.)

    // define an X property
    member this.X with get() = x
                  and set(v) = x <- v

    // define a Y property using an auto-implemented property
    member val Y = yValue with get, set

    // define a function to get the distance from the current point
    abstract GetDistance<'T when 'T :> IPoint2D > : point:'T -> double
    default this.GetDistance<'T when 'T :> IPoint2D>(point:'T) =
        let xDif = this.X - point.X
        let yDif = this.Y - point.Y
        let distance = sqrt (xDif**2. + yDif**2.)
        distance

    // define a static method to return a Point2D from (x,y) tuple
    static member FromXY (x, y) =
        Point2D(x,y)

    // overload the + operator
    static member (+) (point:Point2D, offset:double) =
        Point2D(point.X + offset, point.Y + offset)

    // unary - operator
    static member (~-) (point:Point2D) =
        Point2D(-point.X, -point.Y)

    interface I2DLocation with
        member this.X with get() = this.X and set(v) = this.X <- v
        member this.Y with get() = this.Y and set(v) = this.Y <- v
    interface IPoint2D with
        member this.GetDistance<'T when 'T :> IPoint2D > (p) = this.GetDistance(p)

    override this.ToString() = sprintf "Point2D(%f, %f)" this.X this.Y

Using the operators defined

let c =Point2D(2., 2.)
let d = -c
let originalPoint = d + 2.   // the result is original point (0,0)

When the F# compiler compiles an operator, it generates a method that has a compiler-generated name. If you use reflection, or Microsoft IntelliSense, the generated method name is what you will see. Table 2-3 shows the standard operators and corresponding names.

Table 2-3. Standard operators and generated names

Operator

Generated Name

[]

op_Nil

::

op_Cons

+

op_Addition

op_Subtraction

*

op_Multiply

/

op_Division

@

op_Append

^

op_Concatenate

%

op_Modulus

&&&

op_BitwiseAnd

|||

op_BitwiseOr

^^^

op_ExclusiveOr

<<<

op_LeftShift

~~~

op_LogicalNot

>>>

op_RightShift

~+

op_UnaryPlus

~–

op_UnaryNegation

=

op_Equality

<=

op_LessThanOrEqual

>=

op_GreaterThanOrEqual

<

op_LessThan

>

op_GreaterThan

?

op_Dynamic

?<–

op_DynamicAssignment

|>

op_PipeRight

<|

op_PipeLeft

!

op_Dereference

>>

op_ComposeRight

<<

op_ComposeLeft

<@ @>

op_Quotation

<@@ @@>

op_QuotationUntyped

+=

op_AdditionAssignment

–=

op_SubtractionAssignment

*=

op_MultiplyAssignment

/=

op_DivisionAssignment

..

op_Range

.. ..

op_RangeStep

Other operators not listed are the combination of operator names. For example, +– will be op_PlusMinus. The operator characters and corresponding names are listed in Table 2-4.

Table 2-4. Operator characters and names

Operator Character

Name

>

Greater

<

Less

+

Plus

Minus

*

Multiply

/

Divide

=

Equals

~

Twiddle

%

Percent

.

Dot

&

Amp

|

Bar

@

At

^

Hat

!

Bang

?

Qmark

(

LParen

,

Comma

)

RParen

[

LBrack

]

RBrack

F# provides a broad range of operators, not all of which are supported in C#. Only the following operators are recognized in F#:

  • Binary operators +, –, *, /, &, %, |, >>, <<, +=, –=, /=, %=

  • Unary operators ~+, ~–, ~!, ~++, ~––

You can also define operators at the global level. Example 2-63 defines an operator –?.

Example 2-63. Global-level operator
let inline (-?) (x: int) (y: int) = x - 3*y
printf "%d" (10 -? 1)    // result is 7

let (!++) (seq:int seq) = seq |> Seq.sum
let result = !++ [1..4]

Note

Global operators can decrease the readability of the code. If that is the case, you can download the math symbol add-on for F# from http://visualstudiogallery.msdn.microsoft.com/fe627c2a-5d09-4252-bcc7-300821ae707c. After setting the mapping file to show !++ as , the code in the Visual Studio editor windows will be much cleaner. A sample screen shot is shown here:

image with no caption

Using Delegates and Events

According to the definition in MSDN (http://msdn.microsoft.com/en-us/library/ms173171(v=vs.80).aspx), a delegate is a type-safe way to reference a method. A delegate is similar to a C++ function pointer, but it is type safe. A C++ function pointer can point to any function, but a delegate can point only to the function with the same function signature. The introduction of a delegate-enabled function can be treated like a variable and passed as a parameter. One of the major applications for a delegate is to define an event. Example 2-64 shows how to define a delegate in F#.

Example 2-64. Defining and invoking a delegate type
let f a b = a + b

// define a delegate type
type MyDelegateType = delegate of int * int -> int
let delegateValue = new MyDelegateType(f)

// invoking the function and delegate returns the same result
let fResult = f 1 2
let fromDelegate = delegateValue.Invoke(1,2)

If a class member takes a delegate as a parameter, you can pass in a lambda expression and F# will create a delegate behind the scenes. See Example 2-65.

Example 2-65. Passing a lambda to a class member that takes a delegate type
// define a delegate type
type IntDelegate = delegate of int -> unit

//define a class that takes a delegate parameter
type MyType =
    static member Apply (i:int, d:IntDelegate) =
        d.Invoke(i)

// pass lambda to the function that takes a delegate type
MyType.Apply (0, (fun x -> printfn "%d" x))

Note

The implicit conversion works only on a class member. It will not work on a let binding.

A delegate is similar to an F# function. A delegate provides an extra interesting feature called delegate combination. Combining multiple delegates allows a user to invoke combined functions with a single Invoke call. In Example 2-66, a Combine function and Remove function can be used to edit the coalesced function set.

Example 2-66. Delegate combination
//define a delegate type
type PrintDelegate = delegate of string -> unit

// create delegates
let printOne = PrintDelegate(fun s -> printfn "One %s" s)
let printTwo = PrintDelegate(fun s -> printfn "Two %s" s)

// combine delegates
let printBoth = PrintDelegate.Combine(printOne, printTwo)

// invoke the combined delegates
printBoth.DynamicInvoke("aaa")

Note

A delegate created in F# does not have BeginInvoke and EndInvoke; therefore, you cannot invoke the delegate asynchronously.

An event is just a delegate property on a class. Invoking an event is nothing more than invoking a combined delegate. An event is defined in the Microsoft.FSharp.Control namespace in FSharp.Core.dll. If you take a closer look at the namespace, there are two Event types defined. Both of them provide identical class members. You can use either of them to define your event:

  • Event<’Delegate,’Args>. Event implementations for a delegate type following the standard .NET Framework convention of a first-sender argument

  • Event<’T>. Event implementations for the IEvent<_> type

Note

If you do not remove the event from the object, the reference to the object will prevent garbage collection from reclaiming the memory.

If you look at Example 2-67, the process to define an event in F# is similar to the one in C#. First define a delegate, and then define the event using the delegate. In the example, myEvent is created as an Event<’D, ‘T> type. The Event type supports two methods, Publish and Trigger. Publish is used to publish an event so that the event can be subscribed to, and Trigger is used to raise the event. The CLIEvent attribute is used to make the event type compile as a CLI event, so that you can use += and –= to subscribe to this event from C# code.

Example 2-67. Event<’D, ‘T> sample

Defining a class in F#

// define a delegate
type Delegate = delegate of obj * System.EventArgs -> unit

// define a class with event
type MyClassWithEvent() =
    let myEvent = new Event<Delegate, System.EventArgs>()
    [<CLIEvent>]
    member this.Event = myEvent.Publish
    member this.RaiseEvent (args) = myEvent.Trigger(this, args)

let classWithEvent2 = new MyClassWithEvent()
classWithEvent2.Event.Add(fun (e) -> printfn "message is %A" e)
classWithEvent2.RaiseEvent(System.EventArgs())

C# code using F#

static void Main(string[] args)
{
    var myEventClass2 = new Chapter2.MyClassWithEvent();
    myEventClass2.Event += MyHandler2;
    myEventClass2.RaiseEvent(new EventArgs());
}

private static void MyHandler2(object sender, EventArgs e)
{
    // sender is myEventClass2
}

Example 2-68 provides code that demonstrates how to use Event<’T>, which is a simpler format. The underscore in the Event<’T> definition is a placeholder that is used to inform the F# compiler to figure out the type information. If you compare these two Event implementations, they are different in the following way:

  • The Event<’T> version is simpler.

  • The Event<’D, ‘T> version is closer to C#’s event-definition process.

  • Event<’T> requires adding FSharp.Core.dll as a reference. Otherwise, the compiler complains about not finding the type 'Microsoft.FSharp.Control.FSharpHandler'1<T0>'. Event<’D, ‘T> does not need to reference to FSharp.Core.dll.

  • The sender value in MyHandler2 holds the sender instance, but MyHandler hides the sender information in the first element of the tuple.

Example 2-68. Event<’T> sample

Defining a class in F#

// define a class with event
type MyClassWithCLIEvent() =
    let myEvent = new Event<_>()
    [<CLIEvent>]
    member this.MyEvent = myEvent.Publish
    member this.RaiseEvent(arg) = myEvent.Trigger(this, arg)

let classWithEvent = MyClassWithCLIEvent()

// subscribe the event by using Add
classWithEvent.MyEvent.Add(fun (sender, arg) -> printfn "message is %A" arg)

// raise event
classWithEvent.RaiseEvent("hello world")

Using an F# event from C#

class Program
{
    static void Main(string[] args)
    {
        var myEventClass = new Chapter2.MyClassWithCLIEvent();
        myEventClass.MyEvent += MyHandler;
        myEventClass.RaiseEvent("Hello word");
    }

    private static void MyHandler(object sender, Tuple<MyClassWithCLIEvent,object> args)
    {
        // sender is NULL
        // args.Item1 is the sender
        // args.Item2 is the raised message
    }
}

Note

In C# code, the sender is NULL, and the sender is the first element of tuple. If you have an event on an interface and you want to interoperate with C#, you need to define the CLIEvent attribute on the interface and all implementations.

Interoperating with a C# Project

C# is the de facto standard OOP language on the .NET platform, and thousands of applications have been developed using C#. It is inevitable to have F# code interacting with C#. In this section, I’ll cover how to add a reference and add AssemblyInfo.

Adding a Reference

Adding a reference to C# from F# (or vice versa) can be accomplished by using the Add Reference dialog box. See Figure 2-2.

The Add Reference dialog box
Figure 2-2. The Add Reference dialog box

Note

There are two ways to add a reference to a project in a solution. One is to add a reference to the generated binary file located in inDebug folder. The other way is to add a reference to the project, and Visual Studio can resolve the binary file path. F# supports both approaches, but the project reference cannot be viewed by using Object Browser. To address this limitation, you can directly add a reference to the binary file. If you decide to reference to the DLL directly, please remember to change it back when you build the release binary.

Using AssemblyInfo

AssemblyInfo consists of all of the build options for the project, including the version, company name, GUID, compilers options, and other such information. For a commercial application, this information is very important. Most of the file content consists of assembly-level attributes. The assembly-level attributes must be placed above a do binding. See Example 2-69. You can use 169 to represent the ©. Additionally, because Visual Studio supports Unicode, you can actually put © in the string.

Example 2-69. AssemblyInfo file
open System.Reflection
open System.Runtime.CompilerServices
open System.Runtime.InteropServices

// General information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[<assembly: AssemblyTitle("myApp from © XYZ Company")>]
[<assembly: AssemblyDescription("myApp")>]
[<assembly: AssemblyConfiguration("")>]
[<assembly: AssemblyCompany("XYZ Company")>]
[<assembly: AssemblyProduct("myApp")>]
[<assembly: AssemblyCopyright("© XYZ Company 2012. All rights reserved.")>]
[<assembly: AssemblyCulture("")>]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[<assembly: ComVisible(false)>]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[<assembly: Guid("c95f0dd1-9182-4d48-8bc2-b6cc2bca17bc5")>]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[<assembly: AssemblyVersion("1.0.0325.0003")>]
[<assembly: AssemblyFileVersion("1.0.0325.0003")>]
do ()

Real-World Samples

I’ll finish this chapter with two samples. The first one is a Windows Presentation Foundation (WPF) converter that can pipe the converter function. The other one implements the INotifyCollectionChanged interface, which can substitute the ObservableCollection.

Using the WPF Converter

The WPF converter is a nice feature you can use to convert data before presenting it to the UI. When you have several of these converters, you can start thinking about how to combine them to reduce redundancy. There are some open source frameworks trying to chain the converters. The converter itself is a function. So it is perfect place to use function composition and pipelining to solve the problem. See Example 2-70. The string-to-visibility conversion is performed by using function composition and a pipeline. Compared to other object-oriented implementations, this approach is much simpler.

Example 2-70. WPF converter
namespace FSharp.Converters

open System
open System.Windows
open System.Windows.Data
open Microsoft.FSharp.Reflection

[<AutoOpen>]
module FunctionLibrary =
    let nullFunction = fun value _ _ _ -> value

    let stringToInt (a:Object) = Convert.ToInt32(a)
    let intToBool = fun i -> i = 0
    let boolToVisibility = fun b ->
        if b then Visibility.Visible
        else Visibility.Collapsed

    let convert<'T> f (obj:System.Object) (t:Type) (para:System.Object)
        (culture:Globalization.CultureInfo)  = (obj :?> 'T) |> f |> box

/// abstract class for converter
[<AbstractClass>]
type ConverterBase(convertFunction, convertBackFunction) =
    /// constructor take nullFunction as inputs
    new() = ConverterBase(nullFunction, nullFunction)

    // implement the IValueConverter
    interface IValueConverter with
        /// convert a value to new value
        override this.Convert(value, targetType, parameter, culture) =
            this.Convert value targetType parameter culture

        /// convert a value back
        override this.ConvertBack(value, targetType, parameter, culture) =
            this.ConvertBack value targetType parameter culture

    abstract member Convert : (obj -> Type -> obj -> Globalization.CultureInfo->obj)
    default this.Convert = convertFunction

    abstract member ConvertBack : (obj -> Type -> obj -> Globalization.CultureInfo->obj)
    default this.ConvertBack = convertBackFunction

/// Sample concrete implementation
type StringToVisiblityConverter() =
    inherit ConverterBase(stringToInt >> intToBool >> boolToVisibility |> convert,
nullFunction)

/// debugger converter used to debug the data binding problem(s)
type DebuggerConverter() =
    inherit ConverterBase(nullFunction, nullFunction)

Using ObservableCollection with List Features

If you have been frustrated because ObservableCollection lacks some basic List functionality and does not work well with LINQ, then ObservableList is a solution you might be interested in. In Example 2-71, ObservableList<’T> is derived from a List<’T> type. Additionally, it provides a CollectionChanged event. The CollectionChanged event is fired when an element in the collection changes. It behaves like ObservableCollection<T> but provides LINQ and other List<> specific features.

The code seems lengthy, but it’s actually simple. ObservableCollection<T> notifies the WPF framework of different changes by firing events with different parameters:

  • NotifyCollectionChangedAction.Add generates a notification that one or more items were added to the collection.

  • NotifyCollectionChangedAction.Remove generates a notification that one or more items were removed from the collection.

  • NotifyCollectionChangedAction.Replace generates a notification that one or more items were replaced in the collection.

  • NotifyCollectionChangedAction.Move generates a notification that one or more items were moved in the collection.

  • NotifyCollectionChangedAction.Reset generates a notification that the collection experienced dramatic changes.

Example 2-71. INotifyCollectionChanged implementation
#nowarn "864"

namespace Chapter2RealWorldSample

open System.Collections.Generic
open System.Collections.Specialized

// define ObservableList<'T> class
type ObservableList<'T> () =
    inherit System.Collections.Generic.List<'T>()     //inherit List

    let collectionChanged = Event< _ , _ > ( )

    let mutable isTriggerEvent = false   //mutable variable

    // additional constructor to initialize the instance with given items
    new(items) = ObservableList<'T>() then base.AddRange(items)

    // implement the INotifyCollectionChanged interface
    interface INotifyCollectionChanged with
        [<  CLIEvent  >]
        member this.CollectionChanged = collectionChanged.Publish

    // property to control if any of the events should be triggered
    member this.IsTriggerEvent
        with get() = isTriggerEvent
        and set(v) = isTriggerEvent <- v

    //add element
    member this.Add(n:'T) =
        base.Add(n)
        this.TriggerAdd(n, base.Count)

    // remove element
    member this.Remove(n:'T) =
        let index = this.IndexOf(n)
        if index <> -1 then
            let r = base.Remove(n)
            this.TriggerRemove(n, index)
            r
        else
            false

    member this.Remove(n:'T seq) =
        n |> Seq.iter (this.Remove >> ignore)
    member this.RemoveAt(index) =
        if this.Count > 0 then
            if index >=0 && index < this.Count then
                let item = base.[index]
                base.RemoveAt(index)
                this.TriggerRemove(item, index)
    member this.RemoveRange(index, count) =
        [0..count-1] |> Seq.iter (fun i -> this.RemoveAt(index))

    // sort elements
    member this.Sort()=
        base.Sort()
        this.TriggerReset()
    member this.Sort(comparer:IComparer<'T>) =
        base.Sort(comparer)
        this.TriggerReset()
    member this.Sort(index, count, comparer) =
        base.Sort(index, count, comparer)
        this.TriggerReset()

    // reverse elements in the list
    member this.Reverse() =
        base.Reverse()
        this.TriggerReset()
    member this.Reverse(index, count) =
        base.Reverse(index, count)
        this.TriggerReset()

    // add items
    member this.AddRange(items) =
        items |> Seq.iter this.Add

    // clear current list
    member this.Clear() =
        base.Clear()
        this.TriggerReset()

    // insert element at the index location
    member this.Insert(index, item) =
        base.Insert(index, item)
        this.TriggerAdd(item, index)

    // insert multiple elements at the index location
    member this.InsertRange(index, items) =
       items |> Seq.iteri (fun i item -> this.Insert(index+i, item))

    // define an indexer
    member this.Item
        with get(i) = base.[i]
        and set i v =
            let old = base.[i]
            base.[i] <- v
            this.TriggerReplace(v, old, i)

    // trigger add, remove, replace, and reset events
    member private this.TriggerAdd(item, index) =
        if this.IsTriggerEvent then
            collectionChanged.Trigger(this,
                NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item,
index))
    member private this.TriggerRemove(item, index) =
        if this.IsTriggerEvent then
            collectionChanged.Trigger(this,
                NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
item, index))
    member private this.TriggerReplace(item, oldItem, index) =
        if this.IsTriggerEvent then
            collectionChanged.Trigger(this,    NotifyCollectionChangedEventArgs(NotifyColl
ectionChangedAction.Replace, item, oldItem, index))
    member public this.TriggerReset() =
        if this.IsTriggerEvent then
            collectionChanged.Trigger(this,
                NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset,
null, -1))

    // static function to convert any IEnumerable variable
    static member FromList(items) = ObservableList(items)

Note

If the preceding code is pasted into an empty Program.fs file. You might get the warning, “Main module of program is empty: nothing will happen when it is run.”

From these two samples, F# shows that it can implement the same functionality as C#. You also saw that F# is especially good at function processing. Do not stop here. You’ve seen only the tip of the iceberg! F# has a compelling story awaiting you...

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.51.103