Search code examples
kotlindelegatesdelegation

Delegation to another object of same type using by does not even compile


I am trying to understand how the delegate keyword by works.
So delegating to implemenent an interface is clear e.g.

class Manager(clientele: List<Client> = ArrayList()): List<Client> by clientale

But the following does not work:

data class Client(val name: String, val postalCode: Int)  
fun createClient() = Client("Bob", 1234)
val bigClient: Client by createClient() // compilation error

I get the error:

Missing getValue(Nothing?, KProperty<*>) method delegate of type Client

I thought that if two objects are the same the delegation from one to the other (Client by Client) would work.
Can someone please explain what is the error here and what am I doing wrong?


Solution

  • Unfortunately that's not exactly how delegation of properties works. Based on the documentation:

    For a read-only property (i.e. a val), a delegate has to provide a function named getValue that takes the following parameters:

    • thisRef - must be the same or a supertype of the property owner;
    • property - must be of type KProperty<*> or its supertype.

    For a mutable property (a var), a delegate has to additionally provide a function named setValue that takes the following parameters:

    • thisRef - same as for getValue();
    • property - same as for getValue();
    • newValue - must be of the same type as the property or its subtype.

    [...] Both of the functions need to be marked with the operator keyword.

    So in order just to make your example work, you have to add a getValue() method which meets the above requirements:

    data class Client(val name: String, val postalCode: Int) {
        operator fun getValue(thisRef: Nothing?, property: KProperty<*>): Client = this
    }
    

    You can also use and implement the ReadOnlyProperty and ReadWriteProperty interfaces which provide the required methods:

    data class Client(val name: String, val postalCode: Int) : ReadOnlyProperty<Nothing?, Client> {
        override fun getValue(thisRef: Nothing?, property: KProperty<*>): Client = this
    }
    

    Edit:

    What is this getValue() supposed to do?

    Let me explain a little further on a more abstract example. We have the following classes:

    class MyDelegate : ReadWriteProperty<MyClass, String> {
    
        private var delegateProperty: String = ""
    
        override fun getValue(thisRef: MyClass, property: KProperty<*>): String {
            println("$thisRef delegated getting the ${property.name}'s value to $this")
            return delegateProperty
        }
    
        override fun setValue(thisRef: MyClass, property: KProperty<*>, value: String) {
            println("$thisRef delegated setting the ${property.name}'s value to $this, new value: $value")
            delegateProperty = value
        }
    }
    
    class MyClass {
        var property: String by MyDelegate()
    }
    

    The above MyClass would get compiled more or less to:

    class MyClass {
        private var property$delegate: MyDelegate = MyDelegate()
    
        var property: String
            get() = property$delegate.getValue(this, this::property)
            set(value) = property$delegate.setValue(this, this::property, value)
    }
    

    So you can see that the compiler requires a delegate to have getValue() and setValue() methods for mutable properties (var) or only getValue() for immutable properites (val), because it uses them to respectively get and set the delegated property's value.


    What are Nothing and KProperty<*>?

    KProperty<*> is a Kotlin class which represents a property and provides its metadata.

    Nothing is a type that represents a value that doesn't exist. It's quite irrelevant from the delegation point of view. It came up in this case, because you probably defined the bigClient property outside any class so it has no owner, hence thisRef is Nothing.