Property Delegation

2 minute read

In Kotlin, we could connect a property to a delegate with the by keyword:

class Readable(val n: Int) {
  val value: String by BasicRead()
}

class BasicRead {
  operator fun getValue(
    r: Readable,
    property: KProperty<*>
  ) = "value: ${r.n}"
}

The delegate’s class must contain a getValue() function if the property is a val (read only). If the property is var (read/write), both getValue() and setValue() functions must be present. Because getValue() returns a String, the type of value must also be String.

The second parameter property is a 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().


class ReadWritable(var n: Int) {
  var msg = ""
  var value: String by BasicReadWrite()
}

class BasicReadWrite {

  operator fun getValue(
    rw: ReadWriteable,
    property: KProperty<*>
  ) = "getValue: ${rw.n}"

  operator fun setValue(
    rw: ReadWritable,
    property: KProperty<*>,
    newString: String
  ) {
    rw.n = newString.toIntOrNull() ?: 0
    rw.msg = "setValue to ${rw.n}"
  }

}

val x = ReadWritable(11)
print(x.value) //getValue: 11

x.value = 99
print(x.value) //getValue: 99
print(x.msg) //setValue to 99

Using ReadOnlyProperty interface

The above example doesn’t implement an interface. The class can be used as a delegate if it follows the convention of having necessary function(s) with signature(s). However, we could also implement the ReadOnlyProperty -

class ReadableByInterface(val n: Int) {
  val value: String by BasicReadWithInterface()
  // SAM conversion
  val valueSam: String by ReadOnlyProperty { _, _ -> "getValue: $n" }
}

class BasicReadWithInterface : ReadOnlyProperty<ReadableByInterface, String> {
  override operator fun getValue(
    r: ReadableByInterface,
    property: KProperty<*>
  ) = "value: ${r.n}"
}

Implementing ReadOnlyProperty communicates us that BasicReadWithInterface can be used as a delegate and ensures a proper getValue() definition. Likewise, we could use ReadWriteProperty interface to implement to become a delegate, ensuring proper getValue() and setValue() definitions.

Extension Function as Delegate

getValue() and setValue() can be written as extension functions as well.

class Addition(val a: Int, val b: Int) {
  val sum by Sum()
}

class Sum

operator fun Sum.getValue(
  r: Addition,
  property: KProperty<*>
) = r.a + r.b

val addition = Addition(10, 20)
print(addition.sum) // 30

Why is this important? Using extension, we could use existing class that we are unable to modify or inherit and still delegate a property with it.

Summary

The delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively with zero boilerplate code.

With delegate, we could just implement once, add them to a library and reuse them later. The most common property that we use often is lazy where the value is computed only on first access.