Kotlin: How can I get the delegation class of a member property?
Asked Answered
S

3

5

How can I get the delegation class of a member property?

By this, I mean is it possible to complete such a function:

inline fun <reified T> delegationExample(t: T) {
    for (prop in T::class.declaredMemberProperties) {
        val delegatedClass = // what to do?!
    }
}

Where the delegation class may look like:

class DelegationExample {
    operator fun getValue(ref: Any, prop: KProperty<*>) = 0
}

And the declaration class might look like this:

object Example {
    val a by DelegationExample()
    val b by DelegationExample()
    val c by DelegationExample()
}
Strake answered 10/9, 2016 at 1:18 Comment(1)
Each of the classes KProperty0, KProperty1, and KProperty2 have a getDelegate function. Does that not work? Why are the answers here using reflection when a simple built-in method exists?Bandanna
S
4

To find properties that delegate to a delegate class along with the instance of that class, here is a utility function:

data class DelegatedProperty<T : Any, DELEGATE : Any>(val property: KProperty1<T, *>, val delegatingToInstance: DELEGATE)

inline fun <reified T : Any, DELEGATE : Any> findDelegatingPropertyInstances(instance: T, delegatingTo: KClass<DELEGATE>): List<DelegatedProperty<T, DELEGATE>> {
    return T::class.declaredMemberProperties.map { prop ->
        val javaField = prop.javaField
        if (javaField != null && delegatingTo.java.isAssignableFrom(javaField.type)) {
            javaField.isAccessible = true // is private, have to open that up
            @Suppress("UNCHECKED_CAST")
            val delegateInstance = javaField.get(instance) as DELEGATE
            DelegatedProperty(prop, delegateInstance)
        } else {
            null
        }
    }.filterNotNull()
}

A few notes:

  • First correct your reified type T to T: Any or you cannot access all of the extensions in Kotlin reflection including declaredMemberProperties
  • It is easiest to get to the field from a property reference to be sure you are actually talking about something that is really a property, so for each of declaredMemberProperties use javaField to do so.
  • Since javaField is a custom getter and could be nullable, it is saved to a local variable so smart casting will work later.
  • Then if this field has the same type as the delegation class you are looking for, you can then access the field.
  • But first you have to force the field's accessibility because it is a private field.

Running this in test program:

class DelegationExample {
    operator fun getValue(ref: Any, prop: KProperty<*>) = 0
}

class Example {
    val a by DelegationExample()
    val b by DelegationExample()
    val c by DelegationExample()
}

fun main(args: Array<String>) {
    findDelegatingPropertyInstances(Example(), DelegationExample::class).forEach {
        println("property '${it.property.name}' delegates to instance of [${it.delegatingToInstance}]")
    }
}

The output is something like:

property 'a' delegates to instance of [DelegationExample@2c1b194a]
property 'b' delegates to instance of [DelegationExample@4dbb42b7]
property 'c' delegates to instance of [DelegationExample@66f57048]
Sonometer answered 10/9, 2016 at 14:8 Comment(3)
My patch: gist.github.com/Jire/df9c99fac5e4d3f35e1a6c517716f989Strake
Sure, both do similar things. I wanted to return all things that delegate in this example, and also not call isAccessible on anything I didn't have to (is there impact in calling it more?)Sonometer
The patch you reference (Jire) will only work if there is only one declared property that use the delegate that is passed as the 2nd parameter. Is there anyway to find the delegate for a given property when there are 2 or more properties using the same delegate? I would love to have a function that could do that. I've tried to figure it out but I don't think it's possible.Seldan
C
2

One way to avoid reflection is to first initialize your delegate object and store it as a member of its own, and then delegate your property by it.

object Example {
    val aDelegate = DelegationExample()
    val bDelegate = DelegationExample()
    val cDelegate = DelegationExample()

    val a by aDelegate
    val b by bDelegate
    val c by cDelegate
}
Cakewalk answered 9/11, 2021 at 20:42 Comment(1)
Simple & efficient. ThanksRiga
S
1

On the byte code level delegated properties do not defer from regular ones (public getter/setter and a private field).

One way you could go is scanning the private fields of Example and filtering those which have operator getValue(thisRef: R, KProperty<*>). Technically a field may contain a delegate object val x = lazy {1}, but that is not very likely.

Slapstick answered 10/9, 2016 at 6:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.