如何让Kotlin函数支持可选挂起?避免重复实现重载
能否实现支持可选挂起的Kotlin函数?
以下面这个包裹回调并在其执行前后打印日志的函数为例:
fun withLogs(cb: () -> Unit) { println("Before"); cb(); println("After"); }
普通函数可以正常调用该方法,但挂起函数无法直接传入:
fun unsuspend () { withLogs({ println("my func") }); // 调用正常 } suspend fun suspended() { withLogs(suspend { println("my func") }); // 编译报错 }
我们可以编写显式的重载函数解决问题,但除了suspend关键字外,两份代码逻辑完全重复:
fun withLogs(cb: () -> Unit) { println("Before"); cb(); println("After"); } suspend fun withLogs(cb: suspend () -> Unit) { println("Before"); cb(); println("After"); }
是否可以编写一个能自动适配回调挂起特性的函数?比如类似这样的设想:
<S: suspend> fun withLogs(S cb: () -> Unit) { /* 省略实现 */ } fun unsuspend () { withLogs({ println("my func") }); // 非挂起调用 } suspend fun suspended() { withLogs(suspend { println("my func") }); // 挂起调用 }
目前我找到的临时解决方案是用runBlocking让非挂起版本委托到挂起版本:
fun withLogs(cb: () -> Unit) = runBlocking(withLogs(suspend { cb() }))
但希望有更优雅的实现方式。
优雅实现方案
Kotlin目前不支持你设想的<S: suspend>这类泛型约束语法,因为挂起函数类型属于特殊的函数类型,与普通函数类型在类型系统中是分离的。不过我们可以通过提取公共逻辑+重载的方式避免代码重复,同时保持调用的简洁性:
方案1:提取公共逻辑到私有函数
将日志包裹逻辑抽离到私有工具函数,再编写两个重载版本分别处理普通和挂起回调:
private fun <R> executeWithLogs(block: () -> R): R { println("Before") val result = block() println("After") return result } // 处理普通回调的版本 fun withLogs(cb: () -> Unit) = executeWithLogs(cb) // 处理挂起回调的版本 suspend fun withLogs(cb: suspend () -> Unit) = executeWithLogs { runBlocking { cb() } }
方案2:使用inline函数优化(若允许inline)
如果可以使用inline函数,能进一步减少开销,同时保持代码简洁:
private inline fun <R> executeWithLogs(block: () -> R): R { println("Before") val result = block() println("After") return result } inline fun withLogs(crossinline cb: () -> Unit) = executeWithLogs(cb) inline suspend fun withLogs(crossinline cb: suspend () -> Unit) = executeWithLogs { runBlocking { cb() } }
这两种方案都避免了重复编写日志逻辑,同时让调用方可以像最初期望的那样,直接传入普通或挂起回调,无需额外的包裹代码。
内容的提问来源于stack exchange,提问作者Tiddo
相关产品推荐
相关产品推荐

