5.7.4 上下文参数
原文链接: https://kotlinlang.org/docs/context-parameters.html
5.7.4 上下文参数
提示: 上下文参数取代了此前称为上下文接收者的实验性特性。它们的主要区别见上下文参数的设计文档。要从上下文接收者迁移到上下文参数,可以使用 IntelliJ IDEA 中的辅助支持,详见相关博客文章。
上下文参数允许函数和属性声明那些在周围上下文中隐式可用的依赖项。
有了上下文参数,你就不必再手动传递那些在一系列函数调用之间共享、且很少变化的值,例如服务或依赖项。
要为属性和函数声明上下文参数,请使用 context 关键字,后面跟参数列表,每个参数都声明为 name: Type。下面是一个依赖 UserService 接口的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| // UserService 定义了上下文中需要的依赖
interface UserService {
fun log(message: String)
fun findUserById(id: Int): String
}
// 声明一个带上下文参数的函数
context(users: UserService)
fun outputMessage(message: String) {
// 使用上下文中的 log
users.log("Log: $message")
}
// 声明一个带上下文参数的属性
context(users: UserService)
val firstUser: String
// 使用上下文中的 findUserById
get() = users.findUserById(1)
fun main() {
val users = object : UserService {
override fun log(message: String) {
println(message)
}
override fun findUserById(id: Int): String {
return "User $id"
}
}
context(users) {
outputMessage("Looking up the first user")
println(firstUser)
// User 1
}
}
|
当你不需要直接引用该参数时,可以用 _ 作为上下文参数名。匿名上下文参数仍然可以满足被调用函数所需的上下文参数,但你不能按名称访问它。要显式访问它的值,请使用 contextOf<T>():
1
2
3
4
5
6
7
8
9
10
| // 用 "_" 作为上下文参数名
context(_: UserService)
fun logWelcome() {
// 这个匿名参数满足了 outputMessage() 所需的
// UserService 上下文参数
outputMessage("Welcome!")
// 显式获取 UserService 值
contextOf<UserService>().log("Hi!")
}
|
上下文参数的解析
Kotlin 会在调用处通过在当前作用域中查找匹配的上下文值来解析上下文参数。它按类型进行匹配。如果在同一作用域层级存在多个兼容的值,编译器会报告歧义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // UserService 定义了上下文中需要的依赖
interface UserService {
fun log(message: String)
}
// 声明一个带上下文参数的函数
context(users: UserService)
fun outputMessage(message: String) {
users.log("Log: $message")
}
fun main() {
// 实现 UserService
val serviceA = object : UserService {
override fun log(message: String) = println("A: $message")
}
// 实现 UserService
val serviceB = object : UserService {
override fun log(message: String) = println("B: $message")
}
// 在调用处 serviceA 和 serviceB 都匹配期望的 UserService 类型
context(serviceA, serviceB) {
// 这会导致歧义错误
outputMessage("This will not compile")
}
}
|
显式传递上下文实参
实验性
当多个重载仅在上下文参数上有所不同时,如果有多个匹配的上下文值可用,调用就可能产生歧义。
要解决这种歧义,请在调用处传入显式的上下文实参:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| class EmailSender
class SmsSender
context(emailSender: EmailSender)
fun sendNotification() {
println("Sent email notification")
}
context(smsSender: SmsSender)
fun sendNotification() {
println("Sent SMS notification")
}
context(defaultEmailSender: EmailSender, defaultSmsSender: SmsSender)
fun notifyUser() {
// 选择带 EmailSender 上下文参数的那个重载
sendNotification(emailSender = defaultEmailSender)
// 选择带 SmsSender 上下文参数的那个重载
sendNotification(smsSender = defaultSmsSender)
}
|
你也可以用显式的上下文实参来减少某些函数调用中的嵌套:
- 对于单次调用,使用显式上下文实参可以让调用更易读。
- 如果多次调用使用相同的上下文实参,则使用
context() 函数。
此特性处于实验性阶段。要启用它,请在构建文件中添加以下编译器选项:
Gradle
1
2
3
4
5
| kotlin {
compilerOptions {
freeCompilerArgs.add("-Xexplicit-context-arguments")
}
}
|
Maven
1
2
3
4
5
6
7
8
9
10
11
12
13
| <build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xexplicit-context-arguments</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
|
限制
上下文参数仍在持续改进中,目前的一些限制包括:
- 构造函数不能声明上下文参数。
- 带上下文参数的属性不能有幕后字段或初始化器。
- 带上下文参数的属性不能使用委托。
尽管存在这些限制,上下文参数仍通过简化的依赖注入、更完善的 DSL 设计以及有作用域的操作,让依赖管理变得更加简单。