# Property Delegation
A property can delegate its accessor logic.
You connect a property to a delegate with the by
keyword:
val/var property by delegate
The delegate’s class must contain a getValue()
function if the property is a val
(read only) or getValue()
and setValue()
functions if the property is a var
(read/write). First consider the read-only case:
// PropertyDelegation/BasicRead.kt
package propertydelegation
import atomictest.eq
import kotlin.reflect.KProperty
class Readable(val i: Int) {
val value: String by BasicRead()
}
class BasicRead {
operator fun getValue(
r: Readable,
property: KProperty<*>
) = "getValue: ${r.i}"
}
fun main() {
val x = Readable(11)
val y = Readable(17)
x.value eq "getValue: 11"
y.value eq "getValue: 17"
}
value
in Readable
is delegated to a BasicRead
object. getValue()
takes a Readable
parameter that allows it to access the Readable
—when you say by
it binds the BasicRead
to the whole Readable
object. Notice that getValue()
accesses i
in Readable
.
Because getValue()
returns a String
, the type of value
must also be String
.
The second getValue()
parameter property
is of the special type KProperty
, and this provides reflective information about the delegated property.
If the delegated property is a var
, it must handle both reading and writing, so the delegate class requires both getValue()
and setValue()
:
// PropertyDelegation/BasicReadWrite.kt
package propertydelegation
import atomictest.eq
import kotlin.reflect.KProperty
class ReadWriteable(var i: Int) {
var msg = ""
var value: String by BasicReadWrite()
}
class BasicReadWrite {
operator fun getValue(
rw: ReadWriteable,
property: KProperty<*>
) = "getValue: ${rw.i}"
operator fun setValue(
rw: ReadWriteable,
property: KProperty<*>,
s: String
) {
rw.i = s.toIntOrNull() ?: 0
rw.msg = "setValue to ${rw.i}"
}
}
fun main() {
val x = ReadWriteable(11)
x.value eq "getValue: 11"
x.value = "99"
x.msg eq "setValue to 99"
x.value eq "getValue: 99"
}
The first two setValue()
parameters are the same as getValue()
, and the third is the value on the right side of the =
, which is what we want to set. Both getValue()
and setValue()
must agree on the type that is read and written, which in this case is String
(the type of value
in ReadWriteable
).
Notice that setValue()
accesses i
in ReadWriteable
, and also msg
.
BasicRead.kt
and BasicReadWrite.kt
do not implement an interface
. A class can be used as a delegate if it simply conforms to the convention of having the necessary function(s) with the necessary signature(s). However, you can also implement the ReadOnlyProperty
interface
, as seen here in BasicRead2
:
// PropertyDelegation/BasicRead2.kt
package propertydelegation
import atomictest.eq
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class Readable2(val i: Int) {
val value: String by BasicRead2()
// SAM conversion:
val value2: String by
ReadOnlyProperty { _, _ -> "getValue: $i" }
}
class BasicRead2 :
ReadOnlyProperty<Readable2, String> {
override operator fun getValue(
thisRef: Readable2,
property: KProperty<*>
) = "getValue: ${thisRef.i}"
}
fun main() {
val x = Readable2(11)
val y = Readable2(17)
x.value eq "getValue: 11"
x.value2 eq "getValue: 11"
y.value eq "getValue: 17"
y.value2 eq "getValue: 17"
}
Implementing ReadOnlyProperty
communicates to the reader that BasicRead2
can be used as a delegate and ensures a proper getValue()
definition.
Because ReadOnlyProperty
has only a single member function (and it has been defined as a fun interface
in the standard library), value2
is defined much more succinctly using a [SAM conversion](javascript:void(0)).
BasicReadWrite.kt
can be modified to implement ReadWriteProperty
, ensuring proper getValue()
and setValue()
definitions:
// PropertyDelegation/BasicReadWrite2.kt
package propertydelegation
import atomictest.eq
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class ReadWriteable2(var i: Int) {
var msg = ""
var value: String by BasicReadWrite2()
}
class BasicReadWrite2 :
ReadWriteProperty<ReadWriteable2, String> {
override operator fun getValue(
rw: ReadWriteable2,
property: KProperty<*>
) = "getValue: ${rw.i}"
override operator fun setValue(
rw: ReadWriteable2,
property: KProperty<*>,
s: String
) {
rw.i = s.toIntOrNull() ?: 0
rw.msg = "setValue to ${rw.i}"
}
}
fun main() {
val x = ReadWriteable2(11)
x.value eq "getValue: 11"
x.value = "99"
x.msg eq "setValue to 99"
x.value eq "getValue: 99"
}
Thus, a delegate class must contain either or both of the following functions, which are called when the delegated property is accessed:
For reading:
operator fun getValue(thisRef: T, property: KProperty<*>): V
For writing:
setValue(thisRef: T, property: KProperty<*>, value: V)
If the delegated property is a val
, only the first function is required and ReadOnlyProperty
can be implemented using a [SAM conversion](javascript:void(0)).
The parameters are:
thisRef: T
points to the delegate object, whereT
is the type of that delegate. If you don’t want to usethisRef
in the function, you can effectively disable it by usingAny?
forT
.property: KProperty<*>
provides information about the property itself. The most commonly-used isname
, which produces the field name of the delegated property.value
is the value stored bysetValue()
into the delegated property.V
is the type of that property.
getValue()
and setValue()
can either be defined by convention, or written as implementations of ReadOnlyProperty
or ReadWriteProperty
.
To enable access to private
elements, nest the delegate class:
// PropertyDelegation/Accessibility.kt
package propertydelegation
import atomictest.eq
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class Person(
private val first: String,
private val last: String
) {
val name by // SAM conversion:
ReadOnlyProperty<Person, String> { _, _ ->
"$first $last"
}
}
fun main() {
val alien = Person("Floopy", "Noopers")
alien.name eq "Floopy Noopers"
}
Assuming adequate access to the elements in the delegating class, getValue()
and setValue()
can be written as extension functions:
// PropertyDelegation/Add.kt
package propertydelegation2
import atomictest.eq
import kotlin.reflect.KProperty
class Add(val a: Int, val b: Int) {
val sum by Sum()
}
class Sum
operator fun Sum.getValue(
thisRef: Add,
property: KProperty<*>
) = thisRef.a + thisRef.b
fun main() {
val addition = Add(144, 12)
addition.sum eq 156
}
This way you can use an existing class that you are unable to modify or inherit and still delegate a property with it.
Here, when you set the value of the property, the number stored is the Fibonacci number for that value, using the fibonacci()
function from the [Recursion](javascript:void(0)) atom:
// PropertyDelegation/FibonacciProperty.kt
package propertydelegation
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import recursion.fibonacci
import atomictest.eq
class Fibonacci :
ReadWriteProperty<Any?, Long> {
private var current: Long = 0
override operator fun getValue(
thisRef: Any?,
property: KProperty<*>
) = current
override operator fun setValue(
thisRef: Any?,
property: KProperty<*>,
value: Long
) {
current = fibonacci(value.toInt())
}
}
fun main() {
var fib by Fibonacci()
fib eq 0L
fib = 22L
fib eq 17711L
fib = 90L
fib eq 2880067194370816120L
}
fib
in main()
is a local delegated property—it’s defined inside a function rather than a class. A delegated property can also be defined at file scope.
ReadWriteProperty
’s first generic argument can be Any?
because we never use it to access anything inside Fibonacci
, which would require specific type information. Instead we manipulate the current
property as we can in any member function.
In most of the examples we’ve seen so far, the first parameter of getValue()
and setValue()
are of a specific type. Those delegates were tied to that specific type. Sometimes it is possible to create a general-purpose delegate by ignoring the first type as Any?
. For example, suppose we’d like to store each delegated String
property in a text file named for that property:
// PropertyDelegation/FileDelegate.kt
package propertydelegation
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import checkinstructions.DataFile
class FileDelegate :
ReadWriteProperty<Any?, String> {
override fun getValue(
thisRef: Any?,
property: KProperty<*>
): String {
val file =
DataFile(property.name + ".txt")
return if (file.exists())
file.readText()
else ""
}
override fun setValue(
thisRef: Any?,
property: KProperty<*>,
value: String
) {
DataFile(property.name + ".txt")
.writeText(value)
}
}
This delegate only needs to interact with the file, and doesn’t need anything through thisRef
. We ignore thisRef
by typing it as Any?
, because Any?
has no interesting operations. We are interested in property.name
, which is the name of the field. Now we can automatically create a file associated with each property and store that property’s data in that file:
// PropertyDelegation/Configuration.kt
package propertydelegation
import checkinstructions.DataFile
import atomictest.eq
class Configuration {
var user by FileDelegate()
var id by FileDelegate()
var project by FileDelegate()
}
fun main() {
val config = Configuration()
config.user = "Luciano"
config.id = "Ramalho47"
config.project = "MyLittlePython"
DataFile("user.txt").readText() eq "Luciano"
DataFile("id.txt").readText() eq "Ramalho47"
DataFile("project.txt").readText() eq
"MyLittlePython"
}
Because it can ignore the surrounding type, FileDelegate
is reusable.
Exercises and solutions can be found at www.AtomicKotlin.com.