Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access property delegate in Kotlin

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.

like image 898
Michael Avatar asked Jun 17 '15 15:06

Michael


People also ask

What is property delegation in Kotlin?

Delegation (in computer science) is the assignment of authority from one instance to another. It can operate mutable as well as static relationship between classes, and inheritance, in turn, is based on the constant concept.

How do you use Kotlin delegate?

Android Dependency Injection using Dagger with Kotlin Kotlin supports “delegation” design pattern by introducing a new keyword “by”. Using this keyword or delegation methodology, Kotlin allows the derived class to access all the implemented public methods of an interface through a specific object.

What is a delegate property?

Simply put, delegated properties are not backed by a class field and delegate getting and setting to another piece of code. This allows for delegated functionality to be abstracted out and shared between multiple similar properties – e.g. storing property values in a map instead of separate fields.

What is lazy delegate Kotlin?

lazy() is a function that takes a lambda and returns an instance of Lazy<T> , which can serve as a delegate for implementing a lazy property. The first call to get() executes the lambda passed to lazy() and remembers the result. Subsequent calls to get() simply return the remembered result.


1 Answers

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()
}
like image 158
Kirill Rakhman Avatar answered Oct 11 '22 07:10

Kirill Rakhman