Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Auto-properties and property blocks

Most properties only store a value. An auto-property says so in one line: the declaration ends in its accessors, and the compiler writes both of them over a hidden field.

type Person
    declare property Name as ustring get, set           '' read and written like a field
    declare property Age as int32 = 18 get, private set '' anyone reads, only Person writes
    declare property Id as int32 get, init              '' set once, by the constructor
    declare constructor( byval newId as int32 )
    declare sub Birthday( )
end type

constructor Person( byval newId as int32 )
    Id = newId
end constructor

sub Person.Birthday( )
    Age += 1
end sub

dim as Person p = Person( 42 )
p.Name = "Ann"
p.Birthday( )
print p.Name; ", age"; p.Age; ", id"; p.Id

Output

Ann, age 19, id 42
  • get, set reads and writes like a plain field.
  • get, private set can be read anywhere but written only by the type's own code, as Birthday does. The setter may be less visible than the getter, never more.
  • get, init (or get alone) can be read anywhere and assigned only by a constructor.
  • = 18 gives the hidden field its first value.

Writing a private setter from outside is refused:

type Person
    declare property Age as int32 = 18 get, private set
end type

dim as Person p
p.Age = 30                          '' refused: the setter is private
autopropserror.tiko(6,2): error TKO0625: 'age' is not accessible here

An auto-property has no code of its own. When the setter has a rule to keep, write an ordinary property over a field of your own.

Property blocks

A property block defines the getter and setter together, between property and end property. The type declares them as usual.

type Thermostat
    declare property Celsius( ) as float64
    declare property Celsius( byval value as float64 )
    declare property Fahrenheit( ) as float64
  private:
    celsius_ as float64
end type

'' Both accessors in one block. In set, value is what was assigned;
'' in get, value is what will be returned.
property Thermostat.Celsius as float64
    get
        value = celsius_
    end get
    set
        if value < -273.15 then value = -273.15
        celsius_ = value
    end set
end property

property Thermostat.Fahrenheit as float64
    get
        return celsius_ * 9 / 5 + 32
    end get
end property

dim as Thermostat t
t.Celsius = 21.5
t.Celsius += 2.5                    '' get, add, set
print "celsius"; t.Celsius; ", fahrenheit"; t.Fahrenheit
t.Celsius = -500
print "asked for -500, got "; t.Celsius

Output

celsius 24, fahrenheit 75.2
asked for -500, got -273.15

Inside both sections value is the value crossing the boundary:

  • In set, it is the value being assigned. It can be changed before it is stored, as the absolute-zero check does.
  • In get, it is the result. It starts at the type's default, and whatever it holds at end get is returned. return works too.

Either section can be left out: Fahrenheit has only a get. get and set open a section only when they stand alone on their line.

Static, indexed and namespace properties

type Grid
    declare property Cell( byval r as int32, byval c as int32 ) as int32
    declare property Cell( byval r as int32, byval c as int32, byval value as int32 )
    declare static property Created as int32 get, private set
    declare constructor( )
  private:
    cells_( 0 to 2, 0 to 2 ) as int32
end type

constructor Grid( )
    Created += 1                    '' one count for the whole type
end constructor

property Grid.Cell( byval r as int32, byval c as int32 ) as int32
    return cells_( r, c )
end property

property Grid.Cell( byval r as int32, byval c as int32, byval value as int32 )
    cells_( r, c ) = value
end property

namespace Settings
    dim shared as int32 level_
    declare property Level( ) as int32
    declare property Level( byval value as int32 )
end namespace

property Settings.Level( ) as int32
    return level_
end property

property Settings.Level( byval value as int32 )
    level_ = iif( value > 10, 10, value )
end property

sub Bump( byref n as int32 )
    n += 5
end sub

dim as Grid a, b
a.Cell( 1, 2 ) = 7
Bump( a.Cell( 1, 2 ) )              '' written back through the setter
print "cell"; a.Cell( 1, 2 ); ", grids"; Grid.Created
Settings.Level = 50
print "level"; Settings.Level

Output

cell 12, grids 2
level 10
  • A property can take any number of indices. The setter's value comes last.
  • A static property belongs to the type, not to an object: Grid.Created. It can be an auto-property too.
  • A property can live in a namespace, with no object at all.
  • A property passed to a byref parameter is written back through its setter when the call returns, so Bump changes the cell.
  • Compound assignment (+=, &=, ...) and swap call the getter and then the setter.

Activities

  1. Give Person an Email auto-property that only Person may change, and a SetEmail method that refuses a value with no @.
  2. Turn Settings.Level into a property block over level_.