Делегат по предоставлению недвижимости в Котлине

Kotlin делегировал свойства, что является очень приятной особенностью. Но иногда методов get() и set() недостаточно. Скажем, я хочу создать объект Closeable лениво и закрыть его позже. Вот пример того, как можно реализовать такое свойство делегата:

fun <T : Closeable> closeableLazy(initializer: () -> T) =
        CloseableLazyVal(initializer)

class CloseableLazyVal<T : Closeable>(
    private val initializer: () -> T
) : ReadOnlyProperty<Any?, T> {

    private var value: T? = null

    override fun get(thisRef: Any?, desc: PropertyMetadata): T {
        if (value == null) {
            value = initializer()
        }
        return value
    }

    fun close() {
        value?.close()
    }
}

И вот как я хотел бы использовать его:

private val stream by closeableLazy { FileOutputStream("/path/to/file") }

fun writeBytes(bytes: ByteArray) {
    stream.write(bytes)
}

override fun close() {
    stream::delegate.close() // This line will not compile
}

К сожалению, этот подход не работает, потому что кажется, что Kotlin не позволяет напрямую обращаться к делегатам свойств. Есть ли способ сделать то, что я хочу? Или планируете добавить такие функции в Kotlin, потому что это будет такая опрятная функция.

Ответ 1

Итак, я придумал следующее решение:

fun <T : Closeable> closeableLazy(initializer: () -> T) =
        CloseableLazyVal(initializer)

class CloseableLazyVal<T : Closeable>(
        private val initializer: () -> T
) : ReadOnlyProperty<CloseableDelegateHost, T> {

    private var value: T? = null

    override fun get(thisRef: CloseableDelegateHost, desc: PropertyMetadata): T {
        if (value == null) {
            value = initializer()
            thisRef.registerCloseable(value!!)
        }
        return value!!
    }

}

interface CloseableDelegateHost : Closeable {
    fun registerCloseable(prop : Closeable)
}

class ClosableDelegateHostImpl : CloseableDelegateHost {

    val closeables = arrayListOf<Closeable>()

    override fun registerCloseable(prop: Closeable) {
        closeables.add(prop)
    }

    override fun close() = closeables.forEach { it.close() }
}

class Foo : CloseableDelegateHost by ClosableDelegateHostImpl() {
    private val stream by closeableLazy { FileOutputStream("/path/to/file") }

    fun writeBytes(bytes: ByteArray) {
        stream.write(bytes)
    }

}

Обратите внимание, что метод get свойства имеет параметр thisRef. Я требую, чтобы он наследовал от CloseableDelegateHost, который закроет любой зарегистрированный Closeable, когда он будет закрыт. Чтобы упростить реализацию, я делегирую этот интерфейс простой реализации на основе списка.

UPDATE (скопировано из комментариев): Я понял, вы можете просто объявить делегата как отдельное свойство, а затем делегировать ему второе свойство. Таким образом, вы можете легко получить доступ к делегату.

private val streamDelegate = closeableLazy { FileOutputStream("/path/to/file") }
private val stream by streamDelegate

fun writeBytes(bytes: ByteArray) {
    stream.write(bytes)
}

override fun close() {
    streamDelegate.close()
}

Ответ 2

В Kotlin 1.1 (начиная с бета 2) делегаты могут быть получены из свойств, поэтому теперь вы можете писать

override fun close() {
    (::stream.apply { isAccessible = true }.getDelegate() 
        as CloseableLazyVal<*>).close()
}