Brigadier Kotlin DSL中by限定符仅在run/apply内可用的问题求助
by限定符只能在接收者作用域使用的问题 在集成Brigadier的Kotlin DSL库时,遇到一个限制:库提供的by语法只能在DslCommandBuilder<*>作为当前作用域this时才能使用。示例代码如下:
fun test(builder: DslCommandBuilder<ClientCommandSource>) { builder.apply { val bar by this.integer("bar") // 正常工作 } val bar by builder.integer("bar") // 编译错误,无法使用by }
我们希望在自定义Command的execute方法中,不用手动套builder.run{}就能直接使用DSL语法,比如:
object SetCommand : Command("set", "allows you to configure module settings") { override fun execute(builder: DslCommandBuilder<ClientCommandSource>) { // 希望在这里直接用 val bar by integer("bar") } }
最优解决方案:修改父类Command的execute为带接收者的函数
最优雅的解决方式是调整父类Command的方法签名,让execute成为DslCommandBuilder<ClientCommandSource>的接收者函数,这样子类实现时,作用域内的this就是DslCommandBuilder实例,直接使用库的DSL语法即可。
步骤1:修改父类Command
abstract class Command(name: String, description: String) { // 将原来的execute方法改为带接收者的函数 abstract fun DslCommandBuilder<ClientCommandSource>.execute() }
步骤2:子类实现execute
此时子类重写execute时,自然处于DslCommandBuilder的作用域中,无需额外的run/apply:
object SetCommand : Command("set", "allows you to configure module settings") { override fun DslCommandBuilder<ClientCommandSource>.execute() { // 直接使用DSL语法,无需builder前缀或run块 val bar by integer("bar") val foo by string("foo") // 后续命令逻辑 runs { // 处理参数bar和foo } } }
步骤3:触发execute方法
在需要构建命令的地方,通过run方法将builder作为接收者传入即可:
val commandBuilder = DslCommandBuilder<ClientCommandSource>() commandBuilder.run { SetCommand.execute() }
替代方案(无法修改父类时)
如果因为项目限制无法修改父类Command的结构,可以封装一个扩展函数来简化调用:
// 给Command类添加扩展函数,自动将builder作为接收者传入 fun Command.executeWithReceiver(builder: DslCommandBuilder<ClientCommandSource>) { builder.run { execute(builder) } } // 使用方式 SetCommand.executeWithReceiver(commandBuilder)
不过这种方式还是需要在execute方法内部套run块,不如方案一优雅。
问题根源解释
库中的by语法依赖于Kotlin的委托属性和接收者扩展函数:integer()等方法是DslCommandBuilder的扩展函数,返回的ArgumentDelegate类型的provideDelegate方法需要当前作用域的this是DslCommandBuilder实例,才能正确绑定命令参数到builder。直接调用builder.integer()时,provideDelegate无法获取到正确的接收者上下文,因此无法使用by关键字。
内容的提问来源于stack exchange,提问作者OffeeX

