Home

F# Collections: Arrays - Indexed Properties

   

Introduction

An indexed property is a class/structure's property that allows you to access an item of a list or collection using the features of an array. To create an indexed property, in the body of the class or structure, create a let-binding member that is an array.

The array can be of a primitive type. Here is an example for strings:

type MetroSystem() =
    let lines : string array = [| ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; "" |]

An alternative is to pass an array parameter to the constructor of the class and assign that array to the internal variable.

The primary formula to create a read-only indexed property is:

member self-identifier.PropertyName
    with get(index-variable) = get-function-body

You start with the member keyword that is followed by either this, self, or any identifier of your choice. This is followed by the desired name of the property. The property is nrmally named Item.

To get the read section of the property or a read-only property, create a with get() clause. This time (unlike the previous properties we created so far), the parentheses of the getter must include a parameter. It is a habit or tradition to name that parameter as index but you can name it anything you want. In the body of the getter, you have various alternatives. You can call a function or a method that is in charge of accessing a member of the array variable using an index. Here is an example:

type MetroLine(stations : string array) =
    let lines : string array = stations

    let getter(counter : int) = lines.[counter]

    member this.Item with get(index) = getter(index)

After creating the read clause of the property, outside the class, you can use a for loop to access each item. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

type MetroLine(stations : string array) =
    let lines : string array = stations

    let retrieve(counter : int) = lines.[counter]

    member this.Item with get(index) = retrieve(index)

let metroSystem : Form = new Form(MaximizeBox = false,
                                  Text = "Suburban Metro System",
                                  ClientSize = new System.Drawing.Size(215, 112),
                                  StartPosition = FormStartPosition.CenterScreen)

let lbxLines : ListBox = new ListBox(Location = new Point(12, 17),
                                     Size = new System.Drawing.Size(188, 90))

let redLine = new MetroLine([| "Millbrae"; "San Bruno"; "South San Francisco"; "Colma"; "Daly City" |])

let mutable i = 0

for l in 0 .. 4 do
    i <- lbxLines.Items.Add (sprintf "%s" redLine.[l])

metroSystem.Controls.Add lbxLines

[<EntryPoint>]
let main arguments =
    Application.Run metroSystem
    0

This would produce:

Introduction to Indexed Properties

In most cases, an indexed property manages its own access to the items of the class. In this case, the array variable can be assigned to the getter and that variable must access an item using its index. The formula to follow would be:

type Class/Structure =
    let variable : data-type array = array-values

    member self-identifier.PropertyName with get(index) = variable.[index]

Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

type MetroLine(stations : string array) =
    let lines : string array = stations

    member this.Item with get(index) = lines.[index]

let metroSystem : Form = new Form(MaximizeBox = false,
                                  Text = "Suburban Metro System",
                                  ClientSize = new System.Drawing.Size(215, 112),
                                  StartPosition = FormStartPosition.CenterScreen)

let lbxLines : ListBox = new ListBox(Location = new Point(12, 17),
                                     Size = new System.Drawing.Size(188, 90))

let redLine = new MetroLine([| "Millbrae"; "San Bruno"; "South San Francisco"; "Colma"; "Daly City" |])

let mutable i = 0
for l in 0 .. 4 do
    i <- lbxLines.Items.Add (sprintf "%s" redLine.[l])

metroSystem.Controls.Add lbxLines

[<EntryPoint>]
let main arguments =
    Application.Run metroSystem
    0

In the same way, you can create a write-only property.

A Read-Write Indexed Property

You can have a read/write indexed property that allows you to specify the values of the property and be able to retrieve them when necessary. The formula to create a read/write indexed property that uses functions is:

member self-identifier.PropertyName
    with get(index-variable) = get-function-body
    and set index-variables value-variables = set-function-body

In this case, the setter must take two parameters. One parameter, the first, would represent the index of an item. The second parameter is the item will be positioned in the index of the first parameter. In the body of the setter, you can call a function or a method that can assign a value to an element of an array based on the index of that item. At a minimum, the function or method can take appropriate arguments. Here is an example:

type MetroLine() =
    let lines : string array = [| ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; "" |]

    let retrieve counter = lines.[counter]
    let specify counter name = lines.[counter] <- name

    member this.Item
        with get(index) = retrieve(index)
        and set index item = specify index item

Once this has been done, you can access each item of the internal array based on its index and you can assign the desired value. Here are examples:

open System
open System.Drawing
open System.Windows.Forms 

type MetroLine() =
    let lines : string array = [| ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; "" |]

    let retrieve counter = lines.[counter]
    let specify counter name = lines.[counter] <- name

    member this.Item
        with get(index) = retrieve(index)
        and set index item = specify index item

let metroSystem : Form = new Form(MaximizeBox = false,
                                  Text = "Suburban Metro System",
                                  ClientSize = new System.Drawing.Size(215, 142),
                                  StartPosition = FormStartPosition.CenterScreen)

let lbxLines : ListBox = new ListBox(Location = new Point(12, 17),
                                     Size = new System.Drawing.Size(188, 120))

let redLine = new MetroLine()

redLine.[0] <- "North Concord/Martinez"
redLine.[1] <- "Concord"
redLine.[2] <- "Pleasant Hill/Contra Costa Centre"
redLine.[3] <- "Walnut Creek"
redLine.[4] <- "Lafayette"
redLine.[5] <- "Orinda"
redLine.[6] <- "Rockridge"
redLine.[7] <- "MacArthur"

let mutable i = 0

for l in 0 .. 7 do
    i <- lbxLines.Items.Add (sprintf "%s" redLine.[l])

metroSystem.Controls.Add lbxLines

[<EntryPoint>]
let main arguments =
    Application.Run metroSystem
    0

Of course, as seen with the getter clause, you can create a setter that handles its own assignment. To do this, in the body of the setter, you can simply access the item from the array, based on the index, and assigne the second parameter to it. This can be done as follows:

open System
open System.Drawing
open System.Windows.Forms 

type MetroLine() =
    let lines : string array = [| ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; "" |]

    member this.Item
        with get(index) = lines.[index]
        and set index item = lines.[index] <- item

let metroSystem : Form = new Form(MaximizeBox = false,
                                  Text = "Suburban Metro System",
                                  ClientSize = new System.Drawing.Size(215, 142),
                                  StartPosition = FormStartPosition.CenterScreen)

let lbxLines : ListBox = new ListBox(Location = new Point(12, 17),
                                     Size = new System.Drawing.Size(188, 120))

let redLine = new MetroLine()

redLine.[0] <- "North Concord/Martinez"
redLine.[1] <- "Concord"
redLine.[2] <- "Pleasant Hill/Contra Costa Centre"
redLine.[3] <- "Walnut Creek"
redLine.[4] <- "Lafayette"
redLine.[5] <- "Orinda"
redLine.[6] <- "Rockridge"
redLine.[7] <- "MacArthur"

let mutable i = 0

for l in 0 .. 7 do
    i <- lbxLines.Items.Add (sprintf "%s" redLine.[l])

metroSystem.Controls.Add lbxLines

[<EntryPoint>]
let main arguments =
    Application.Run metroSystem
    0

This would produce:

A Read-Write Indexed Property

     
 

Indexed Properties and Classes

Instead of primitive types, the items of an array can be objects from records, structure, or classes. In the same way, the items of an indxed property can use records or objects. You can use one of the many built-in .NET Framework classes or you must first create your record or class. Here is an example of a record:

type Station = {
    Name       : string
    Location   : string
    CarParking : bool
    BikeRacks  : bool }

As stated already, to start creating an indexer, declare a let-binding array variable in the class. The variable will (or must) be based on the desired record, structure, or class. When creating the indexed property, use the array the same way we did for primitive types. Here is an example:

type Station = {
    Name     : string
    Location : string
    CarParking : bool
    BikeRacks  : bool }

type MetroLine() =
    let lines : Station array = Array.zeroCreate 10

    member this.Item
        with get(index) = lines.[index]
        and set index item = lines.[index] <- item

In this case, we created a read/write property. This means that you can use it to assign objects as individual items of the indexed property. When retriving the items of the property, you can access each element of the property, which produces an object of the record, structure, or class, and you can use it as you see fit. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

type Station = {
    Name       : string
    Location   : string
    CarParking : bool
    BikeRacks  : bool }

type MetroLine() =
    let lines : Station array = Array.zeroCreate 10

    member this.Item
        with get(index) = lines.[index]
        and set index item = lines.[index] <- item

let rockville  = { Name = "Rockville"; Location = "Rockville, MD"; CarParking = true; BikeRacks = true }
let twinbrook  = { Name = "Twinbrook"; Location = "Rockville, MD"; CarParking = true; BikeRacks = true }
let shadyGrove = { Name = "Shady Grove"; Location = "Rockville, MD"; CarParking = true; BikeRacks = true }
let whiteFlint = { Name = "White Flint"; Location = "Rockville, MD"; CarParking = true; BikeRacks = true }
let glenmont   = { Name = "Glenmont"; Location = "Silver Spring, MD"; CarParking = true; BikeRacks = true }
let grosvenor  = { Name = "Grosvenor"; Location = "Bethesda, MD"; CarParking = true; BikeRacks = true }
let medicalCenter = { Name = "Medical Center"; Location = "Bethesda, MD"; CarParking = true; BikeRacks = true }

let redLine = new MetroLine()
redLine.[0] <- shadyGrove
redLine.[1] <- rockville
redLine.[2] <- twinbrook
redLine.[3] <- whiteFlint
redLine.[4] <- grosvenor
redLine.[5] <- glenmont


let metroSystem : Form = new Form(MaximizeBox = false,
                                  Text = "Suburban Metro System",
                                  ClientSize = new System.Drawing.Size(345, 142),
                                  StartPosition = FormStartPosition.CenterScreen)
// List View: Lines
let colName = new ColumnHeader(Text = "Name", Width = 80)
let colLocation = new ColumnHeader(Text = "Location", Width = 80)
let colParking = new ColumnHeader(Text = "Parking", TextAlign = HorizontalAlignment.Center)
let colBikeRacks = new ColumnHeader(Text = "Bike Racks", TextAlign = HorizontalAlignment.Center, Width = 80)

let lvwLines = new System.Windows.Forms.ListView(FullRowSelect = true, GridLines = true,
                                                 Size = new System.Drawing.Size(314, 112),
                                                 Location = new Point(15, 15), View = View.Details)
lvwLines.Columns.AddRange [| colName; colLocation; colParking; colBikeRacks |]
metroSystem.Controls.Add lvwLines

let mutable i = 0

for n in 0 .. 5 do
    let line = redLine.[n]
    let lviStation : ListViewItem = new ListViewItem(line.Name)
    lviStation.SubItems.Add(line.Location) |> ignore
    lviStation.SubItems.Add(sprintf "%A" line.CarParking) |> ignore
    lviStation.SubItems.Add(sprintf "%A" line.BikeRacks) |> ignore
    lvwLines.Items.Add(lviStation) |> ignore

[<STAThread>]
[<EntryPoint>]
let main arguments =
    Application.Run metroSystem
    0

This would produce:

Indexed Properties and Classes

Of course, you can add other properties to the class and access themoutside the class as normal properies. Here is an example where a class includes an extra property named Name:

open System
open System.Drawing
open System.Windows.Forms 

type Station = {
    Name       : string
    Location   : string
    CarParking : bool
    BikeRacks  : bool }

and MetroLine(name) =
    let nm = ref name
    let lines : Station array = Array.zeroCreate 10

    member this.Name with get() = !nm and set(designation) = nm := designation

    member this.Item
        with get(index) = lines.[index]
        and set index item = lines.[index] <- item

let rockville       = { Name = "Rockville"; Location = "Rockville, MD"; CarParking = true; BikeRacks = true }
let twinbrook       = { Name = "Twinbrook"; Location = "Rockville, MD"; CarParking = true; BikeRacks = true }
let pentagon        = { Name = "Pentagon"; Location = "Arlington, VA"; CarParking = false; BikeRacks = true }
let greenbelt       = { Name = "Greenbelt"; Location = "Greenbelt, MD"; CarParking = true; BikeRacks = true }
let anacostia       = { Name = "Anacostia"; Location = "Washington, DC"; CarParking = true; BikeRacks = true }
let shadyGrove      = { Name = "Shady Grove"; Location = "Rockville, MD"; CarParking = true; BikeRacks = true }
let whiteFlint      = { Name = "White Flint"; Location = "Rockville, MD"; CarParking = true; BikeRacks = true }
let huntington      = { Name = "Huntington"; Location = "Huntington, VA"; CarParking = true; BikeRacks = true }
let glenmont        = { Name = "Glenmont"; Location = "Silver Spring, MD"; CarParking = true; BikeRacks = true }
let branchAvenue    = { Name = "Branch Avenue"; Location = "Suitland, MD"; CarParking = true; BikeRacks = true }
let grosvenor       = { Name = "Grosvenor-Strathmore"; Location = "Bethesda, MD"; CarParking = true; BikeRacks = true }
let congressHeights = { Name = "Congress Heights"; Location = "Washington, DC "; CarParking = false; BikeRacks = true }


let redLine = new MetroLine("Red")
redLine.[0] <- shadyGrove
redLine.[1] <- rockville
redLine.[2] <- twinbrook
redLine.[3] <- whiteFlint
redLine.[4] <- grosvenor
redLine.[5] <- glenmont

let yellowLine = new MetroLine("Yellow")
yellowLine.[0] <- huntington
yellowLine.[1] <- pentagon
yellowLine.[2] <- greenbelt

let greenLine = new MetroLine("Green")
greenLine.[0] <- branchAvenue
greenLine.[1] <- anacostia
greenLine.[2] <- greenbelt
greenLine.[3] <- congressHeights

let metroSystem : Form = new Form(MaximizeBox = false,
                                  Text = "Suburban Metro System",
                                  ClientSize = new System.Drawing.Size(453, 242),
                                  StartPosition = FormStartPosition.CenterScreen)
// List View: Lines
let colStationName = new ColumnHeader(Text = "Station Name", Width = 120)
let colLineName = new ColumnHeader(Text = "Line", Width = 80)
let colLocation = new ColumnHeader(Text = "Location", Width = 80)
let colParking = new ColumnHeader(Text = "Parking", TextAlign = HorizontalAlignment.Center)
let colBikeRacks = new ColumnHeader(Text = "Bike Racks", TextAlign = HorizontalAlignment.Center, Width = 80)

let lvwLines = new System.Windows.Forms.ListView(FullRowSelect = true, GridLines = true,
                                                 Size = new System.Drawing.Size(424, 210),
                                                 Location = new Point(15, 15), View = View.Details)
lvwLines.Columns.AddRange [| colStationName; colLineName; colLocation; colParking; colBikeRacks |]
metroSystem.Controls.Add lvwLines

let mutable i = 0

for n in 0 .. 5 do
    let line = redLine.[n]
    let lviStation : ListViewItem = new ListViewItem(line.Name)
    lviStation.SubItems.Add(redLine.Name) |> ignore
    lviStation.SubItems.Add(line.Location) |> ignore
    lviStation.SubItems.Add(sprintf "%A" line.CarParking) |> ignore
    lviStation.SubItems.Add(sprintf "%A" line.BikeRacks) |> ignore
    lvwLines.Items.Add(lviStation) |> ignore

for n in 0 .. 2 do
    let lviStation : ListViewItem = new ListViewItem(yellowLine.[n].Name)
    lviStation.SubItems.Add(yellowLine.Name) |> ignore
    lviStation.SubItems.Add(yellowLine.[n].Location) |> ignore
    lviStation.SubItems.Add(sprintf "%A" yellowLine.[n].CarParking) |> ignore
    lviStation.SubItems.Add(sprintf "%A" yellowLine.[n].BikeRacks) |> ignore
    lvwLines.Items.Add(lviStation) |> ignore

for n in 0 .. 3 do
    let lviStation : ListViewItem = new ListViewItem(greenLine.[n].Name)
    lviStation.SubItems.Add(greenLine.Name) |> ignore
    lviStation.SubItems.Add(greenLine.[n].Location) |> ignore
    lviStation.SubItems.Add(sprintf "%A" greenLine.[n].CarParking) |> ignore
    lviStation.SubItems.Add(sprintf "%A" greenLine.[n].BikeRacks) |> ignore
    lvwLines.Items.Add(lviStation) |> ignore

[<STAThread>]
[<EntryPoint>]
let main arguments =
    Application.Run metroSystem
    0

This would produce:

Indexed Properties and Classes

   
   
 

Home Copyright © 2012-2015, FunctionX Home