下面给你写一篇 通俗易懂、可直接用于博客/技术文档的《.NET 8 中的 Keyed Services:解锁依赖注入的新方式(最新推荐)》。
内容包含:原理、使用场景、代码示例、与传统 DI 对比、最佳实践。
🚀 .NET 8 中的 Keyed Services:解锁依赖注入的新方式(最新推荐)
在 .NET 8 中,微软正式为内置依赖注入(Microsoft.Extensions.DependencyInjection)引入了 Keyed Services。
这是一个非常重要的升级,让 .NET 原生支持“同类型服务的多实现选择”。
简单来说:
Keyed Services = 给依赖注入的服务“打标签” → 同时注册多个实现 → 按 key 获取特定实例。
以前我们需要:
- 自己写工厂
- 或使用 Scrutor / Autofac 解决
- 或注入 IEnumerable 再手动挑选
现在只需要原生语法即可。
1️⃣ 为什么需要 Keyed Services?
下面的场景非常常见:
❌ 你有多个实现:
interface IPayment { void Pay(); }
class WechatPay : IPayment { … }
class Alipay : IPayment { … }
class Paypal : IPayment { … }
以前你想根据业务规则动态选择 Pay 服务,需要:
- 注入所有实现 (
IEnumerable<IPayment>) - 自己写 switch OR 查字典
- 或自己写工厂
现在只需要:
builder.Services.AddKeyedTransient<IPayment, WechatPay>("wechat");
builder.Services.AddKeyedTransient<IPayment, Alipay>("alipay");
builder.Services.AddKeyedTransient<IPayment, Paypal>("paypal");
然后根据 key 获取即可:
public class PayController
{
private readonly IPayment _payment;
public PayController([FromKeyedServices("wechat")] IPayment payment)
{
_payment = payment;
}
}
这就是 Keyed Services 的核心能力。
2️⃣ Keyed Services 的四种注册方式
.NET 8 内置四种:
| 类型 | 方法 |
|---|---|
| Transient | AddKeyedTransient |
| Scoped | AddKeyedScoped |
| Singleton | AddKeyedSingleton |
| 实例注册 | AddKeyedSingleton(key, instance) |
示例:
builder.Services.AddKeyedTransient<IPayment, WechatPay>("wechat");
builder.Services.AddKeyedTransient<IPayment, Alipay>("alipay");
builder.Services.AddKeyedScoped<IPayment, Paypal>("paypal");
3️⃣ 解析 Keyed 服务的三种方式
✔ 方式 1:构造函数注入 + [FromKeyedServices](最常用)
public class PayController
{
private readonly IPayment _payment;
public PayController([FromKeyedServices("alipay")] IPayment payment)
{
_payment = payment;
}
}
✔ 方式 2:工厂解析(运行时根据条件动态选择)
public class PaymentFactory
{
private readonly IServiceProvider _sp;
public PaymentFactory(IServiceProvider sp)
{
_sp = sp;
}
public IPayment Get(string key)
=> _sp.GetKeyedService<IPayment>(key)!;
}
注入:
var payment = paymentFactory.Get("wechat");
payment.Pay();
这是官方原生支持的“轻量级策略工厂”。
✔ 方式 3:TryGetKeyedService(更安全)
if (_sp.TryGetKeyedService<IPayment>("paypal", out var pay))
{
pay!.Pay();
}
4️⃣ 与传统 DI 的对比
| 特性 | 原生 DI(旧) | Keyed Services(新) |
|---|---|---|
| 多个实现 | 不支持 | ✔ 支持 |
| 选择特定实现 | 麻烦、需 IEnumerable + 手动筛选 | ✔ 原生按键选择 |
| 可读性 | 差 | ✔ 清晰 |
| 工厂模式 | 需要手写 | ✔ 原生支持 |
| 框架原生支持 | ❌ | ✔ .NET 8 原生支持 |
Keyed Services 本质上为原生 DI 完成了最缺的那一块拼图。
5️⃣ 最佳实践(务必收藏)
- 避免使用字符串 key,推荐 enum 或常量
public enum PayType { Wechat, Alipay, Paypal }
注册:
builder.Services.AddKeyedTransient<IPayment, WechatPay>(PayType.Wechat);
- 工厂统一管理 keyed services(更优雅)
public class PaymentFactory
{
private readonly IServiceProvider _sp;
public PaymentFactory(IServiceProvider sp) => _sp = sp;
public IPayment Create(PayType type)
=> _sp.GetKeyedService<IPayment>(type)!;
}
- KeyedService + Options 非常强大
builder.Services.AddKeyedSingleton<IPayment>("wechat", (sp, key) =>
{
var opt = sp.GetRequiredService<IOptions<WechatOptions>>().Value;
return new WechatPay(opt);
});
- 不可与 Named HttpClient 混淆
KeyedService 是通用的;HttpClient 有专门的命名 HttpClient。
6️⃣ 完整示例(可直接复制)
注册
builder.Services.AddKeyedTransient<IPayment, WechatPay>("wechat");
builder.Services.AddKeyedTransient<IPayment, Alipay>("alipay");
builder.Services.AddKeyedTransient<IPayment, Paypal>("paypal");
builder.Services.AddSingleton<PaymentFactory>();
服务
public class PayController
{
private readonly PaymentFactory _factory;
public PayController(PaymentFactory factory)
{
_factory = factory;
}
[HttpGet("pay/{channel}")]
public string Pay(string channel)
{
var payment = _factory.Create(channel);
return payment.Pay();
}
}
工厂
public class PaymentFactory
{
private readonly IServiceProvider _sp;
public PaymentFactory(IServiceProvider sp)
{
_sp = sp;
}
public IPayment Create(string key)
=> _sp.GetKeyedService<IPayment>(key)!;
}
📌 总结
Keyed Services 解决了 .NET DI 最大的痛点之一:多实现选择。
优势包括:
- 原生支持多实现的注入与选择
- 不需手写工厂(也支持工厂)
- 更易扩展
- 更易阅读
- 避免注册冲突
它会成为今后 .NET 开发中特别常见的 DI 写法。
发表回复