简体   繁体   English

如何实现适用于任意类型的属性委托

[英]How to implement property delegate that works with an arbitrary type

I'm trying to implement custom property delegate to store props in a Map (just like in docs , for the educational purposes)我正在尝试实现自定义属性委托以将道具存储在 Map 中(就像在docs中一样,出于教育目的)

Delegate implementation must:委托实施必须:

  1. allow to store properties of arbitrary type允许存储任意类型的属性
  2. 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:但是,它有两个明显的问题:

  1. Using "Any?"使用“任何?” breaks type inference打破类型推断
  2. Explicitly specifying property type var myProp: Int by propInMapDelegate leads to errors: var myProp: Int by propInMapDelegate会导致错误:

error#1错误 #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错误#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.我尝试使用 generics 来进行类型推断,但失败了。

Changing PropInMapDelegate implementation like so:像这样更改PropInMapDelegate实现:

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导致:错误#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错误#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:问题:

  1. What am I missing?我错过了什么?
  2. Why does Kotlin expect the exact "Example" type in setValue/getValue signature?为什么 Kotlin 期望 setValue/getValue 签名中的确切“Example”类型?
  3. How to implement "getValue" without using unsafe "as T" operator?如何在不使用不安全的“as T”运算符的情况下实现“getValue”? Edit: it seems that original implementation utilizes as too, so it's probably ok?编辑:似乎原始实现也使用as它,所以它可能没问题?
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)

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.由于ExampleAny的子类型,因此您的getValue() function 签名的那部分很好。

The part that doesn't match is that you are returning T?不匹配的部分是您要返回T? but you're trying to use it for a non-nullable property.但您正试图将它用于不可为 null 的属性。

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:或者,如果您将运算符 function 更改为返回不可为 null 的值,它将起作用:

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.请注意,如果 map 中不存在该键的值并且您将其用于不可为 null 的属性,则转换as T将失败,因为null无法转换为不可为 null 的 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.关于标记为 3 的问题:如您在标准库源代码中所见,如果您使用任意值类型的 map,则强制转换是不可避免的。 It's part of the potential danger in using a map for this purpose.这是为此目的使用 map 的潜在危险的一部分。 It is also possible the map doesn't have the values at all that match up with the requested property names.也有可能 map 根本没有与请求的属性名称匹配的值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM