I'm trying to implement custom property delegate to store props in a Map (just like in docs, for the educational purposes)
Delegate implementation must:
- allow to store properties of arbitrary type
- allow for type inference (just like the original one)
I've wrote a code that satisfies first condition:
import kotlin.reflect.KProperty
fun main() {
val e = Example(PropInMapDelegate())
e.myProp = 1;
println(e.myProp) // e.myProp is "Any?" :(
}
class Example(propInMapDelegate: PropInMapDelegate) {
var myProp by propInMapDelegate
}
class PropInMapDelegate {
private val _map: HashMap<String, Any?> = HashMap()
operator fun getValue(thisRef: Any, property: KProperty<*>): Any? {
return _map[property.name]
}
operator fun setValue(thisRef: Any, property: KProperty<*>, value: Any?) {
_map[property.name] = value
}
}
But, it has obvious 2 issues:
- Using "Any?" breaks type inference
- Explicitly specifying property type
var myProp: Int by propInMapDelegate
leads to errors:
error#1
Property delegate must have a 'getValue(Example, KProperty*>)' method. None of the following functions are suitable.
getValue(Any?, KProperty<*>) defined in PropInMapDelegate
error#2
Property delegate must have a 'setValue(Example, KProperty*>, Int)' method. None of the following functions are suitable.
setValue(Any?, KProperty<*>, Any?) defined in PropInMapDelegate
I've tried to employ generics to allow for type inference, but failed.
Changing PropInMapDelegate
implementation like so:
class PropInMapDelegate {
private val _map: HashMap<String, Any?> = HashMap()
operator fun <T>getValue(thisRef: Any, property: KProperty<*>): T? {
return _map[property.name] as T // That feels very wrong
}
operator fun <T>setValue(thisRef: Any, property: KProperty<*>, value: T?) {
_map[property.name] = value
}
}
And using the following
class Example(propInMapDelegate: PropInMapDelegate) {
var myProp: Int by propInMapDelegate // error (see below)
}
Leads to: error#1
Property delegate must have a 'getValue(Example, KProperty*>)' method. None of the following functions are suitable.
getValue(Any, KProperty<*>) where T = Int for operator fun <T> getValue(thisRef: Any, property: KProperty<*>): T? defined in PropInMapDelegate
error#2
Property delegate must have a 'setValue(Example, KProperty*>, Int)' method. None of the following functions are suitable.
setValue(Any, KProperty<*>, Int?) where T = Int for operator fun <T> setValue(thisRef: Any, property: KProperty<*>, value: T?): Unit defined in PropInMapDelegate
Questions:
- What am I missing?
- Why does Kotlin expect the exact "Example" type in setValue/getValue signature?
- How to implement "getValue" without using unsafe "as T" operator?
Edit: it seems that original implementation utilizes
as
too, so it's probably ok?
public inline operator fun <V, V1 : V> MutableMap<in String, out @Exact V>.getValue(thisRef: Any?, property: KProperty<*>): V1 =
@Suppress("UNCHECKED_CAST") (getOrImplicitDefault(property.name) as V1)
CodePudding user response:
I think the error message is misleading you because of the way it is worded. "Example" isn't the part that doesn't match. Since Example
is a subtype of Any
, that part of your getValue()
function signature is fine.
The part that doesn't match is that you are returning T?
but you're trying to use it for a non-nullable property.
If you change your property to be nullable, it will work:
var myProp: Int? by propInMapDelegate
Or if you change your operator function to return a non-nullable, it will work:
operator fun <T> getValue(thisRef: Any, property: KProperty<*>): T {
return _map[property.name] as T
}
Note that casting as T
will fail if the value for that key doesn't exist in the map and you're using this for a non-nullable property, because null
cannot be cast to a non-nullable T.
Regarding your question marked 3: As you can see in the standard library source code, casting is unavoidable if you are using a map of arbitrary value types. It's part of the potential danger in using a map for this purpose. It is also possible the map doesn't have the values at all that match up with the requested property names.