Kotlin 中 KProperty1 的通用扩展

Mor*_*oth 5 reflection kotlin kotlin-reflect

我有以下代码:

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
}
Run Code Online (Sandbox Code Playgroud)

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

hot*_*key 3

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

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

例如,这个调用:

Person::age eq 1.2f
Run Code Online (Sandbox Code Playgroud)

实际上相当于:

Person::age.eq<Person, Any>(1.2f)
Run Code Online (Sandbox Code Playgroud)

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

因此,每当类型不完全匹配时,就会选择一个公共超类型R。目前,没有正确的方法来告诉编译器它不应该回退到超类型(请参阅此 Q&A)。

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

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

还相关: