How can provideDelegate select among different delegate implementations at creation time? Sketch an implementation that picks a delegate based on the property's annotations or name.
answer
- return value IS the delegate -> branch per property
- common supertype: ReadOnlyProperty / ReadWriteProperty
- prop.annotations needs kotlin-reflect; prop.name does not
- selection is permanent, not per-access
- backbone of typed DSL/ORM column APIs
basics
~20 sBecause provideDelegate returns the real delegate, it can inspect the property and return different delegate objects for different properties. For example, look at the property's name or annotations and return a cached, computed, or read-only delegate accordingly.
solid answer
~30 sprovideDelegate runs once with the KProperty<*>, and its return value becomes the delegate. So you can branch: read prop.annotations (or prop.name) and return one of several delegate types — e.g., a memoizing delegate for @Cached, a plain one otherwise — as long as they share a common supertype like ReadOnlyProperty<R, V>. Note prop.annotations on a KProperty<*> requires kotlin-reflect at runtime; prop.name does not. The selected delegate is fixed for that property's lifetime. This pattern underpins typed DSLs/ORMs: a single factory expression after by yields per-property-tailored delegates, with validation and selection centralized in one operator.
code
kotlin · 13 linesclass Store(val backing: MutableMap<String, Any?>) {
operator fun provideDelegate(thisRef: Any?, p: KProperty<*>): ReadWriteProperty<Any?, Any?> =
if (p.name.startsWith("ro")) ReadOnlyBacked(p.name, backing)
else MutableBacked(p.name, backing)
}
class MutableBacked(val k: String, val m: MutableMap<String, Any?>) : ReadWriteProperty<Any?, Any?> {
override fun getValue(t: Any?, p: KProperty<*>) = m[k]
override fun setValue(t: Any?, p: KProperty<*>, v: Any?) { m[k] = v }
}
class ReadOnlyBacked(val k: String, val m: MutableMap<String, Any?>) : ReadWriteProperty<Any?, Any?> {
override fun getValue(t: Any?, p: KProperty<*>) = m[k]
override fun setValue(t: Any?, p: KProperty<*>, v: Any?) { error("read-only: ${p.name}") }
}go deeper
Understands the return value is the delegate, even if not the selection pattern.
Can branch on prop.name and return a shared ReadOnlyProperty supertype.
Implements annotation-driven selection and knows the kotlin-reflect cost and the permanence of the choice.
Designs it as a library factory surface, weighing reflection footprint vs name conventions and the one-time selection contract.
## The core capability Since `provideDelegate`'s **return value** is the delegate, and it receives the `KProperty<*>`, it can act as a **factory that branches per property**. The only constraint is that every branch returns a type the compiler accepts as a delegate — typically a shared supertype such as `ReadOnlyProperty<R, V>` or `ReadWriteProperty<R, V>` from `kotlin.properties`. ## Selecting by annotation ```kotlin @Target(AnnotationTarget.PROPERTY) annotation class Cached class Source(private val load: (String) -> String) { operator fun provideDelegate( thisRef: Any?, prop: KProperty<*> ): ReadOnlyProperty<Any?, String> { val cached = prop.annotations.any { it is Cached } // needs kotlin-reflect return if (cached) MemoizingDelegate(prop.name, load) else ReadOnlyProperty { _, _ -> load(prop.name) } } } private class MemoizingDelegate( private val key: String, private val load: (String) -> String ) : ReadOnlyProperty<Any?, String> { private val value by lazy { load(key) } // computed once, then reused override fun getValue(thisRef: Any?, property: KProperty<*>) = value } class Page(src: Source) { @Cached val header: String by src // memoizing delegate selected val footer: String by src // plain delegate selected } ``` ## Selecting by name You can equally branch on `prop.name` (no reflection dependency): ```kotlin return when { prop.name.startsWith("secret") -> EncryptedDelegate(prop.name) else -> PlainDelegate(prop.name) } ``` ## Reflection cost caveat - `prop.name` — **cheap**, baked into metadata, no `kotlin-reflect`. - `prop.annotations`, `prop.returnType`, resolving getters — **require the `kotlin-reflect` artifact** at runtime; omit it and these throw `KotlinReflectionNotSupportedError`. So annotation-based selection has a deployment cost; name-based selection does not. ## Why this is powerful for libraries One `by source` declaration can yield a delegate **tailored to each property** — encrypted, cached, validated, name-mapped — with all the selection logic centralized in a single `operator fun`. This is the backbone of typed configuration libraries and lightweight ORM/DSL column declarations, where the property name and annotations drive how the value is stored and retrieved. ## Pitfalls - Keep return types unified under one delegate interface, or the compiler can't bind. - The selection is **permanent** for that property instance — provideDelegate won't re-run, so don't put per-access decisions here. - Don't reach for `kotlin-reflect` annotations if a name convention suffices.
- What runtime dependency does inspecting prop.annotations require?kotlin-reflect. Without it, accessing annotations/returnType throws KotlinReflectionNotSupportedError; prop.name still works.
- Can the selected delegate change later if the annotation changes?No. provideDelegate runs once; the chosen delegate is fixed for that property instance's lifetime.
saying these in an interview costs you the question
- Returning incompatible types from different branches so the compiler can't bind
- Believing prop.annotations works without kotlin-reflect
- Putting per-access logic in provideDelegate expecting it to re-run
- Thinking you need separate by-expressions per property to vary the delegate