简体   繁体   中英

Create a var using a delegate that does not have a setter

I am trying to create delegate var properties with a delegate that does not provide a setValue(...) method. In other words, I need a property that I can reassign but that should get its value via the delegate as long as it hasn't been reassigned.

I am using the xenomachina CLI arguments parser library, which uses delegates. This works well as long as I have val properties. In some cases I need to be able to change those properties dynamically at runtime, though, requiring a mutable var . I can't simply use a var here, as the library does not provide a setValue(...) method in its delegate responsible for the argument parsing.

Ideally, I'd like something like this:

class Foo(parser: ArgParser) {
    var myParameter by parser.flagging(
        "--my-param",
        help = "helptext"
    )
}

which doesn't work due to the missing setter.

So far, I've tried extending the Delegate class with a setter extension function, but internally it also uses a val , so I can't change that. I've tried wrapping the delegate into another delegate but when I do that then the library doesn't recognize the options I've wrapped anymore. Although I may have missed something there. I can't just re-assign the value to a new var as follows:

private val _myParameter by parser.flagging(...)
var myParameter = _myParameter

since that seems to confuse the parser and it stops evaluating the rest of the parameters as soon as the first delegate property is accessed. Besides, it is not particularly pretty.

How do you use delegates that don't include a setter in combination with a var property?

Here is how you can wrap a ReadOnlyProperty to make it work the way you want:

class MutableProperty<in R, T>(
    // `(R, KProperty<*>) -> T` is accepted here instead of `ReadOnlyProperty<R, T>`,
    // to enable wrapping of properties which are based on extension function and don't
    // implement `ReadOnlyProperty<R, T>`
    wrapped: (R, KProperty<*>) -> T
) : ReadWriteProperty<R, T> {
    private var wrapped: ((R, KProperty<*>) -> T)? = wrapped // null when field is assigned
    private var field: T? = null

    @Suppress("UNCHECKED_CAST") // field is T if wrapped is null
    override fun getValue(thisRef: R, property: KProperty<*>) =
        if (wrapped == null) field as T
        else wrapped!!(thisRef, property)

    override fun setValue(thisRef: R, property: KProperty<*>, value: T) {
        field = value
        wrapped = null
    }
}

fun <R, T> ReadOnlyProperty<R, T>.toMutableProperty() = MutableProperty(this::getValue)

fun <R, T> ((R, KProperty<*>) -> T).toMutableProperty() = MutableProperty(this)

Use case:

var lazyVar by lazy { 1 }::getValue.toMutableProperty()

And here is how you can wrap a property delegate provider:

class MutableProvider<in R, T>(
    private val provider: (R, KProperty<*>) -> (R, KProperty<*>) -> T
) {
    operator fun provideDelegate(thisRef: R, prop: KProperty<*>): MutableProperty<R, T> =
        provider(thisRef, prop).toMutableProperty()
}

fun <T> ArgParser.Delegate<T>.toMutableProvider() = MutableProvider { thisRef: Any?, prop ->
    provideDelegate(thisRef, prop)::getValue
}

Use case:

var flagging by parser.flagging(
    "--my-param",
    help = "helptext"
).toMutableProvider()

You could wrap your delegate with a class like this:

class DefaultDelegate<T>(private val default: Delegate<T>){
    private var _value: T? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T? = 
        _value?: default.value

    operator fun setValue(thisRef: Nothing?, property: KProperty<*>, value: T?) {
        _value = value
    }
}

Usage:

class Foo(parser: ArgParser) {
    var myParameter: Boolean? by DefaultDelegate(parser.flagging(
        "--my-param",
        help = "helptext"
    ))
}

If you need nullability:

class DefaultDelegate<T>(private val default: Delegate<T>){
    private var modified = false
    private var _value: T? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T? = 
        if (modified) _value else default.value

    operator fun setValue(thisRef: Nothing?, property: KProperty<*>, value: T?) {
        _value = value
        modified = true
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM