.NET 8 中的 KeyedService

.NET 8 中的 KeyedService:新特性解析与使用示例

一、引言

在 .NET 8 的 Preview 7 版本中,引入了 KeyedService 支持。这一特性为开发者提供了按名称(name)获取服务的便利,在某些场景下,开发者无需再自行创建工厂类来管理服务。接下来,我们将深入探讨 KeyedService 的使用方法、特殊情况以及存在的一些问题。

二、基本使用示例

1. 简单示例代码

csharp 复制代码
var serviceCollection = new ServiceCollection();
serviceCollection.AddKeyedSingleton<IUserIdProvider, EnvironmentUserIdProvider>("env");
serviceCollection.AddKeyedSingleton<IUserIdProvider, NullUserIdProvider>("");

using var services = serviceCollection.BuildServiceProvider();
var userIdProvider = services.GetRequiredKeyedService<IUserIdProvider>("");
Console.WriteLine(userIdProvider.GetUserId());

var envUserIdProvider = services.GetRequiredKeyedService<IUserIdProvider>("env");
Console.WriteLine(envUserIdProvider.GetUserId());

file interface IUserIdProvider
{
    string GetUserId();
}
file sealed class EnvUserIdProvider : IUserIdProvider
{
    public string GetUserId() => Environment.MachineName;
}
file sealed class NullUserIdProvider : IUserIdProvider
{
    public string GetUserId() => "(null)";
}

2. 代码解释

上述代码展示了 KeyedService 的基本使用。我们通过 AddKeyedSingleton 方法注册了两个不同的 IUserIdProvider 实现,并分别使用不同的键("env" 和 "")进行标识。然后,通过 GetRequiredKeyedService 方法根据键来获取相应的服务实例。

3. 输出结果分析

运行代码后,输出结果为:

复制代码
(null)
WEIHANLI - SURFACE

这表明我们成功地根据不同的键获取到了对应的服务实例,并调用了其方法。

三、特殊的 serviceKey:AnyKey

1. 使用 AnyKey 捕获未注册的 serviceKey

csharp 复制代码
var serviceCollection = new ServiceCollection();
serviceCollection.AddKeyedSingleton<IUserIdProvider, NullUserIdProvider>(KeyedService.AnyKey);

using var services = serviceCollection.BuildServiceProvider();
var userIdProvider = services.GetRequiredKeyedService<IUserIdProvider>("");
Console.WriteLine(userIdProvider.GetUserId());

var envUserIdProvider = services.GetRequiredKeyedService<IUserIdProvider>("env");
Console.WriteLine(envUserIdProvider.GetUserId());

2. 代码解释

这里我们使用 KeyedService.AnyKey 来注册服务。当我们获取服务时,即使使用了未注册的键(如 "" 和 "env"),也不会报错,而是使用 AnyKey 注册的服务。

3. 输出结果及对象验证

输出结果为:

复制代码
(null)
(null)

为了验证不同键获取的服务实例是否为同一个对象,我们添加了以下代码:

csharp 复制代码
Console.WriteLine("userIdProvider == envUserIdProvider ?? {0}", userIdProvider == envUserIdProvider);

输出结果为:

复制代码
userIdProvider == envUserIdProvider ?? False

这表明不同的 serviceKey 获取的是不同的对象。

4. serviceKey 为 null 的情况

serviceKeynull 时,情况比较特殊。在当前的 API 设计中,虽然允许 serviceKeynull,但实际上这会导致问题。例如:

csharp 复制代码
var nullUserIdProvider = services.GetRequiredKeyedService<IUserIdProvider>(null);
Console.WriteLine(nullUserIdProvider.GetUserId());

会抛出异常:

复制代码
System.InvalidOperationException: No service for type 'Net8Sample.<__Script>FE1DBF3BE6F8384813B223E3EAA03DBABDC4153F95C5B3EBB0E0807E84E7C20E4__IUserIdProvider' has been registered.

这说明当 serviceKeynull 时,并不会像使用 AnyKey 那样获取服务,而是直接报错。并且,如果注册 keyed service 时使用 null 作为 serviceKey,实际上相当于注册了一个非 keyed service。

四、构造方法中的 ServiceKeyAttribute

1. 示例代码

csharp 复制代码
var serviceCollection = new ServiceCollection();
serviceCollection.AddKeyedTransient<MyNamedService>(KeyedService.AnyKey);
using var services = serviceCollection.BuildServiceProvider();
Console.WriteLine(services.GetRequiredKeyedService<MyNamedService>("Foo").Name);
Console.WriteLine(services.GetRequiredKeyedService<MyNamedService>("Hello").Name);

file sealed class MyNamedService
{
    public MyNamedService([ServiceKey] string name)
    {
        Name = name;
    }

    public string Name { get; }
}

2. 代码解释

在构造方法中,我们可以使用 ServiceKeyAttribute 来获取注册的 serviceKey。在上述示例中,我们使用 KeyedService.AnyKey 注册服务,然后通过不同的键获取服务实例,并输出构造方法中获取的 serviceKey

3. 输出结果

复制代码
Foo
Hello

这表明我们成功地在构造方法中获取到了实际使用的 serviceKey

4. 类型一致性问题

需要注意的是,构造方法中的 serviceKey 类型和获取服务时的类型应该保持一致,否则会抛出异常。例如:

csharp 复制代码
Console.WriteLine(services.GetRequiredKeyedService<MyNamedService>(123).Name);

会导致异常:

复制代码
System.InvalidOperationException: The type of the key used for lookup doesn't match the type in the constructor parameter with the ServiceKey attribute.

5. serviceKey 类型的灵活性

虽然需要类型一致,但 serviceKeyobject 类型,因此可以使用任意类型。例如:

csharp 复制代码
var serviceCollection = new ServiceCollection();
serviceCollection.AddKeyedTransient<MyKeyedService>(KeyedService.AnyKey);
using var services = serviceCollection.BuildServiceProvider();

Console.WriteLine(services.GetRequiredKeyedService<MyKeyedService>(new Category()
{
    Id = 1,
    Name = "test"
}).Name);

会输出 test

五、Scoped Service 的问题

1. 示例代码及异常

csharp 复制代码
var serviceCollection = new ServiceCollection();
serviceCollection.AddKeyedScoped<IUserIdProvider, NullUserIdProvider>("");
using var services = serviceCollection.BuildServiceProvider();

using var scope = services.CreateScope();
var newId = scope.ServiceProvider.GetRequiredKeyedService<IIdGenerator>("").NewId();
Console.WriteLine(newId);

运行上述代码会抛出异常:

复制代码
System.InvalidOperationException: This service provider doesn't support keyed services.

2. 问题分析

这表明目前对于 scoped service 的支持存在问题。在 aspnetcore 中,基于 HttpContext.RequestServices 获取 keyedService 也会出现同样的问题,因为 HttpContext.RequestServices 是一个 scoped service provider。不过,已经有 PR 修复了这个问题,预计在 RC1 版本中发布。

六、结合 Options 使用 KeyedService

1. 示例代码

csharp 复制代码
var serviceCollection = new ServiceCollection();

serviceCollection.Configure<TotpOptions>(x =>
{
    x.Salt = "1234";
});
serviceCollection.AddKeyedTransient<ITotpService, TotpService>(KeyedService.AnyKey,
    (sp, key) =>
    new TotpService(sp.GetRequiredService<IOptionsMonitor<TotpOptions>>()
        .Get(key is string name ? name : Options.DefaultName)));

using var services = serviceCollection.BuildServiceProvider();
var totpService = services.GetRequiredKeyedService<ITotpService>(string.Empty);
Console.WriteLine("Totp1: {0}", totpService.GetCode("Test1234"));
var totpService2 = services.GetRequiredKeyedService<ITotpService>("test");
Console.WriteLine("Totp2: {0}", totpService2.GetCode("Test1234"));

2. 代码解释

通过结合 Options,我们可以方便地实现基于选项的命名服务。在上述示例中,我们根据不同的键获取不同的 ITotpService 实例,并调用其 GetCode 方法。

3. 输出结果

复制代码
Totp1: 356934
Totp2: 626994

七、总结与见解

1. 优点

KeyedService 解决了一些命名服务的痛点,让开发者可以更方便地按名称获取服务,减少了手动创建工厂类的工作量。结合 Options 使用时,还能实现更灵活的服务配置。

2. 不足

然而,目前该特性还存在一些问题,如 serviceKey 可以为 null 的设计不太合理,scoped service 支持存在 bug 等。不过考虑到这是预览版,这些问题是可以接受的,希望在正式版中能够得到妥善解决。

总体而言,KeyedService 是 .NET 8 中一个很有潜力的特性,为服务管理提供了新的思路和方法。开发者可以在项目中尝试使用,但在正式项目中使用时需要谨慎考虑其稳定性。 ======================================================================

前些天发现了一个比较好玩的人工智能学习网站,通俗易懂,风趣幽默,可以了解了解AI基础知识,人工智能教程,不是一堆数学公式和算法的那种,用各种举例子来学习,读起来比较轻松,有兴趣可以看一下。
人工智能教程

相关推荐
上海云盾-小余12 小时前
BGP 高防底层原理:TCP 异常流量识别与清洗机制
运维·网络·tcp/ip
captain37612 小时前
▲网络原理(2)-TCP
java·服务器·网络·tcp/ip·java-ee
言乐613 小时前
Python加速器4跨境网络加速器
运维·服务器·开发语言·网络·python
蓝速科技16 小时前
医院导诊 AI 数字人一体机场景适配与落地指南丨蓝速科技
运维·数据库·人工智能·科技·自然语言处理·技术分享
H_oRIZoN_17 小时前
Linux入门DAY41(51 单片机 串口与通信协议)
linux·运维·单片机
唐青枫17 小时前
别只会看日志:C#.NET SOS 调试扩展从 Dump 到内存泄漏排查
c#·.net
小马同学-18 小时前
OpenStack 使用实战:Web 界面与 CLI 命令行实验
运维·云计算·openstack
网安老伯19 小时前
都2026年了,还在问网络安全怎么入门?看完这一篇你就懂了
运维·计算机网络·web安全·网络安全·wireshark·密码学·网络攻击模型
小祺先生20 小时前
mt7921 debian系统 如何安装驱动
运维·网络·debian
m0_4598729220 小时前
Linux 系统基础
linux·运维·服务器