首页 > 解决方案 > Kotlin 中 KProperty1 的通用扩展

问题描述

我有以下代码:

import kotlin.reflect.KProperty1


infix fun <T, R> KProperty1<T, R>.eq(value: R) {
    println(this.name + " = $value")
}

infix fun <T, R> KProperty1<T, R>.eq(value: KProperty1<T, R>) {
    println(this.name + " = " + value.name)
}

data class Person(val age: Int, val name: String, val surname: String?)

fun main() {
    Person::age eq 1  // Valid. First function
    Person::name eq "Johan"  // Valid. First function
    Person::name eq Person::name  // Valid. Second function
    Person::age eq 1.2f  // Valid, but it shouldn't be. First function
    Person::age eq Person::name  // Valid, but it shouldn't be. First function
    Person::surname eq null  // Invalid, but it should be. Second function, but it should be first
}

我正在尝试KProperty1使用泛型为类创建扩展函数,但我的泛型与我期望的不匹配。main()列出了这两个函数的一些用途,最后有 3 个意想不到的结果,并描述了我应该期待什么。

标签: reflectionkotlinkotlin-reflect

解决方案


首先,请注意KProperty1<T, out R>具有out-projected 类型参数R,因此KProperty1<Foo, Bar>也可以在KProperty1<Foo, Baz>预期 a 的地方使用 的实例,其中Baz是 的超类型Bar,例如Any

这正是eq使用不完全匹配的类型调用时发生的情况:使用了更常见的超类型,以便正确解析调用。

例如,这个调用:

Person::age eq 1.2f

等效于:

Person::age.eq<Person, Any>(1.2f)

您甚至可以自动将其转换为这种形式,即用普通调用替换中缀调用,然后添加显式类型参数

因此,只要类型不完全匹配,就会为R. 目前,没有适当的方法告诉编译器它不应该退回到超类型(请参阅此 Q&A)。

最后一次调用是类型推断当前工作方式的副作用:编译器似乎必须在决定它是否兼容可空性之前选择其中一个重载。如果你null用它代替(null as String?)它。请在kotl.in/issue上为此提出问题。

通常,最好不要将通用签名与非通用签名混合,因为可能的通用替换会导致歧义。在您的情况下,替换R := KProperty1<T, Foo>(即eq在这种类型的属性上使用)会导致歧义。

也相关:


推荐阅读