Создайте переменную с помощью делегата, у которого нет сеттера

Я пытаюсь создать свойства var делегата с делегатом, который не предоставляет метод setValue(...). Другими словами, мне нужно свойство, которое я могу переназначить, но которое должно получить свое значение через делегата, если оно не было переназначено.

Я использую библиотеку парсера аргументов CLI xenomachina, которая использует делегаты. Это хорошо работает, пока у меня есть свойства val. Однако в некоторых случаях мне нужно иметь возможность динамически изменять эти свойства во время выполнения, что требует изменяемого var. Я не могу просто использовать здесь var, так как библиотека не предоставляет метод setValue(...) в своем делегате, отвечающем за синтаксический анализ аргумента.

В идеале я хотел бы что-то вроде этого:

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

который не работает из-за отсутствующего сеттера.

До сих пор я пытался расширить класс Delegate с помощью функции расширения установки, но внутри он также использует val, поэтому я не могу это изменить. Я попытался обернуть делегата в другой делегат, но когда я это сделаю, библиотека больше не распознает параметры, которые я обернул. Хотя может я что-то там упустил. Я не могу просто переназначить значение новой переменной следующим образом:

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

так как это, кажется, сбивает синтаксический анализатор, и он перестает оценивать остальные параметры, как только происходит доступ к первому свойству делегата. Кроме того, это не особенно красиво.

Как вы используете делегатов, которые не включают установщик в сочетании со свойством var?


person Jocbe    schedule 16.10.2019    source источник


Ответы (2)


Вот как вы можете обернуть ReadOnlyProperty, чтобы заставить его работать так, как вы хотите:

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)

Вариант использования:

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

А вот как вы можете обернуть поставщика делегата свойства:

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
}

Вариант использования:

var flagging by parser.flagging(
    "--my-param",
    help = "helptext"
).toMutableProvider()
person Bananon    schedule 16.10.2019
comment
К сожалению, используя ваше решение, синтаксический анализатор ArgParser, похоже, больше не распознает поле как параметр CLI. Это просто больше недопустимая опция, она не отображается в тексте справки. Каким-то образом синтаксический анализатор должен запутаться, используя такой делегат-обертку — у вас есть какие-нибудь идеи? - person Jocbe; 15.11.2019
comment
@Jocbe Я обновил свой ответ. Пожалуйста, проверьте еще раз. Вы должны использовать parser.flagging(...).toMutableProvider() вместо parser.flagging(...).toMutableProperty(). - person Bananon; 15.11.2019

Вы можете обернуть делегата таким классом:

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

Применение:

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

Если вам нужна обнуляемость:

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
    }
}
person Tenfour04    schedule 16.10.2019
comment
Не уверен, как работает ваша библиотека, так что, возможно, она задохнется от этого? - person Tenfour04; 16.10.2019
comment
Ваше решение не будет работать правильно, если я переназначу свойство на null. Следующий код не работает: var param: Boolean? by DefaultDelegate(parser.flagging("--my-param", help = "helptext")); param = null; param shouldEql null. - person Bananon; 16.10.2019
comment
Я не знал, что вам нужно обнуление. Смотрите обновленную версию. - person Tenfour04; 16.10.2019
comment
Подожди секунду... ты не ОП. - person Tenfour04; 16.10.2019
comment
У меня были такие же проблемы с вашим решением, как и с другим: к сожалению, при использовании вашего решения синтаксический анализатор ArgParser, похоже, больше не распознает поле как параметр CLI. Это просто больше недопустимая опция, она не отображается в тексте справки. Каким-то образом синтаксический анализатор должен запутаться, используя такой делегат-обертку — у вас есть какие-нибудь идеи? Кроме того, мне пришлось изменить тип thisRef в setValue на Any?, чтобы он скомпилировался - это правильно? - person Jocbe; 15.11.2019