15

Kotlin has delegated properties which is a very nice feature. But sometimes get() and set() methods are not enough. Let's say I want to create a Closeable object lazily and to close it later. Here's an example of how such delegate property could be implemented:

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()
    }
}

And that's how I would like to use it:

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
}

Unfortunately, this approach doesn't work because it seems that Kotlin doesn't allow to access property delegates directly. Is there any way to do what I want? Or are there any plans to add such functionality to Kotlin because it would be such a neat feature.

Vadzim
  • 24,954
  • 11
  • 143
  • 151
Michael
  • 53,859
  • 22
  • 133
  • 139

2 Answers2

8

Ok, so I came up with the following solution:

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)
    }

}

Notice, that the property's get method has a parameter thisRef. I require that it inherits from CloseableDelegateHost which will close any registered Closeables when it is closed. To simplify the implementation I delegate this interface to a simple list-based implementation.

UPDATE (copied from comments): I realized, you can just declare the delegate as a separate property and then delegate the second property to it. This way you can access the delegate itself easily.

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()
}
Michael
  • 53,859
  • 22
  • 133
  • 139
Kirill Rakhman
  • 42,195
  • 18
  • 124
  • 148
  • 1
    thank you for the answer. Your solution is nice and solves the problem in this particular case but it doesn't work in all cases. Let's say, for example, I want to close different `Closeable` object at different moments. To handle such situation I have to add some kind of keys to your implementation to make `close()` more granular. In my opinion such functionality must be provided by the language itself without all these hacks. – Michael Jun 18 '15 at 13:09
  • 1
    Another problem I usually face is when I need to check if `Delegate.lazy` property has been initialized. This problem could be easily solved if we had access to the property's delegate, but all walk-arounds seem to be ugly. – Michael Jun 18 '15 at 13:15
  • 2
    I realized, you can just declare the delegate as a separate property and then delegate the second property to it. This way you can access the delegate itself easily. – Kirill Rakhman Jul 03 '15 at 09:56
  • that's an interesting workaround. In this case I will have to delegate fields with the same value in the class by it seems to be a working solution. Until such functionality is added to the language core, your solution may be the best way to achieve what I want. – Michael Jul 03 '15 at 13:57
8

In Kotlin 1.1 (since beta 2), delegates can be retrieved from properties, so you can now write

override fun close() {
    (::stream.apply { isAccessible = true }.getDelegate() 
        as CloseableLazyVal<*>).close()
}
Ingo Kegel
  • 46,523
  • 10
  • 71
  • 102