下面给你写一篇 通俗易懂、可直接用于博客/技术文档的《.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&lt;IPayment, WechatPay>("wechat");
builder.Services.AddKeyedTransient&lt;IPayment, Alipay>("alipay");
builder.Services.AddKeyedTransient&lt;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 内置四种:

类型方法
TransientAddKeyedTransient
ScopedAddKeyedScoped
SingletonAddKeyedSingleton
实例注册AddKeyedSingleton(key, instance)

示例:

builder.Services.AddKeyedTransient&lt;IPayment, WechatPay>("wechat");
builder.Services.AddKeyedTransient&lt;IPayment, Alipay>("alipay");
builder.Services.AddKeyedScoped&lt;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&lt;IPayment>(key)!;
}

注入:

var payment = paymentFactory.Get("wechat");
payment.Pay();

这是官方原生支持的“轻量级策略工厂”。

✔ 方式 3:TryGetKeyedService(更安全)

if (_sp.TryGetKeyedService&lt;IPayment>("paypal", out var pay))
{
    pay!.Pay();
}


4️⃣ 与传统 DI 的对比

特性原生 DI(旧)Keyed Services(新)
多个实现不支持✔ 支持
选择特定实现麻烦、需 IEnumerable + 手动筛选✔ 原生按键选择
可读性✔ 清晰
工厂模式需要手写✔ 原生支持
框架原生支持✔ .NET 8 原生支持

Keyed Services 本质上为原生 DI 完成了最缺的那一块拼图。


5️⃣ 最佳实践(务必收藏)

  1. 避免使用字符串 key,推荐 enum 或常量
public enum PayType { Wechat, Alipay, Paypal }

注册:

builder.Services.AddKeyedTransient&lt;IPayment, WechatPay>(PayType.Wechat);

  1. 工厂统一管理 keyed services(更优雅)
public class PaymentFactory
{
    private readonly IServiceProvider _sp;
    public PaymentFactory(IServiceProvider sp) => _sp = sp;

    public IPayment Create(PayType type)
        => _sp.GetKeyedService&lt;IPayment>(type)!;
}

  1. KeyedService + Options 非常强大
builder.Services.AddKeyedSingleton&lt;IPayment>("wechat", (sp, key) =>
{
    var opt = sp.GetRequiredService&lt;IOptions&lt;WechatOptions>>().Value;
    return new WechatPay(opt);
});

  1. 不可与 Named HttpClient 混淆
    KeyedService 是通用的;HttpClient 有专门的命名 HttpClient。

6️⃣ 完整示例(可直接复制)

注册

builder.Services.AddKeyedTransient&lt;IPayment, WechatPay>("wechat");
builder.Services.AddKeyedTransient&lt;IPayment, Alipay>("alipay");
builder.Services.AddKeyedTransient&lt;IPayment, Paypal>("paypal");
builder.Services.AddSingleton&lt;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&lt;IPayment>(key)!;
}


📌 总结

Keyed Services 解决了 .NET DI 最大的痛点之一:多实现选择。
优势包括:

  • 原生支持多实现的注入与选择
  • 不需手写工厂(也支持工厂)
  • 更易扩展
  • 更易阅读
  • 避免注册冲突

它会成为今后 .NET 开发中特别常见的 DI 写法。