Избегайте неконтролируемого броска - PullRequest
0 голосов
/ 26 апреля 2020

Я не могу найти способ избежать неконтролируемого приведения в следующем случае

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 (или одним из ее подтипов) как получатель, но в то же время я хочу получить ссылку на фактическую реализацию Event при регистрации лямбды. Таким образом, я могу использовать члены его реализации без приведения каждый раз.

Пример (requestId - это поле RequestExpiredEvent):

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

Я знаю, что «потребитель» Лямбда "не имеет ковариации и что они противоречивы, но я думаю, есть ли способ. Я нашел уродливый обходной путь:

    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 голосов
/ 26 апреля 2020

Это опасный состав. SomeEventSubtype.() -> Unit не является подтипом Event.() -> Unit. Это наоборот.

Предположим, что Event - это открытый класс, и у вас есть этот подкласс:

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

Теперь вы пытаетесь разыграть его:

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

Когда вы попытайтесь вызвать helloCasted.invoke(Event()), он сгенерирует исключение ClassCastException, когда попытается привести ваше Событие к SubEvent. Вы не можете вызвать hello с любым экземпляром Event в качестве входа, потому что только SubEvent имеет функцию hello для вызова.

Компилятор поймает эту ошибку, если вы попытаетесь неявно произнесите это:

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

Это работает наоборот. Глядя на входные данные функций, вы можете думать об иерархии типов как о инвертированной.

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}")
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...