避免未经检查的演员

时间:2020-04-26 01:13:44

标签: generics kotlin casting

在以下情况下,我找不到避免未经检查的演员表的方法

class EventBus {
    val eventToHandle: MutableMap<KClass<out Event>, Event.() -> Unit> = mutableMapOf()

    final inline fun <reified T : Event> register(noinline handler: T.() -> Unit) {
        @Suppress("UNCHECKED_CAST")
        eventToHandle[T::class] = handler as Event.() -> Unit
    }

    fun fire(event: Event) {
        eventToHandle[event::class]?.invoke(event)
            ?: throw IllegalStateException("Missing handler for class ${event::class}")
    }
}

我的目标是强制地图仅包含具有Event(或其子类型之一)作为接收器的lambda,但同时我希望引用 actual 注册lambda时实现Event。 这样,我可以使用其实现的成员而无需每次都进行强制转换。

一个示例(requestIdRequestExpiredEvent的字段):

eventBus.register<RequestExpiredEvent> {
            requestService.setExpiredByRequestId(requestId)
        }

我知道“消费者lambda”没有协方差,并且它们是协变的,但是我想是否有办法。 我发现一个丑陋的解决方法是:

    final inline fun <reified T : Event> register(crossinline block: T.() -> Unit) {
        val handler: Event.() -> Unit = { this as T; block(this) }
        eventToHandle[T::class] = handler
    }

谢谢

1 个答案:

答案 0 :(得分:0)

这很危险。 SomeEventSubtype.() -> Unit不是Event.() -> Unit的子类型。相反。

假设事件是一个开放类,并且您有此子类:

class SubEvent: Event() {
    fun hello(): Unit {
        println("hello")
    }
}

现在您尝试投射它:

val hello: SubEvent.() -> Unit = SubEvent::hello
val helloCasted = hello as Event.() -> Unit

当您尝试调用helloCasted.invoke(Event())时,它将尝试将您的事件强制转换为子事件时将引发ClassCastException。您无法使用hello的任何实例作为输入来调用Event,因为只有SubEvent才具有hello的调用功能。

如果您尝试隐式转换它,编译器将捕获此错误:

val hello: SubEvent.() -> Unit = SubEvent::hello
val helloCasted: Event.() -> Unit = hello // compiler error

它确实可以反过来工作。当查看函数的 inputs 时,可以认为类型层次结构是倒置的。

val toStringFun: Event.() -> Unit = Event::toString
val toStringCasted: SubEvent.() -> Unit = toStringFun // OK

如果不在某处进行未经检查的转换,则无法解决此问题,因为您在地图中存储了不同类型的对象。但是您需要将演员表移至fire函数,以便它知道将其投射到什么内容。您可以在地图中将函数存储为Any类型,因为无论如何都要转换它们。像这样:

class EventBus {
    val eventToHandle: MutableMap<KClass<out Event>, Any> = mutableMapOf()

    inline fun <reified T : Event> register(noinline handler: T.() -> Unit) {
        eventToHandle[T::class] = handler
    }

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Event> fire(event: T) {
        (eventToHandle[T::class] as? T.() -> Unit)?.invoke(event)
            ?: throw IllegalStateException("Missing handler for class ${event::class}")
    }
}