Denpendcy Injection 8.0新功能——KeyedService

Denpendcy Injection 8.0新功能------KeyedService

本文只介绍 .NET Denpendcy Injection 8.0新功能------KeyedService,假定读者已熟练使用之前版本的功能。

注册带Key的类

8.0之前,注册一个类往往是AddSingleton<IFoo, Foo>(),8.0添加了一个新功能:"可以注册一个带Key的类 "AddKeyedSingleton<IFoo, Foo>("keyA")。获取服务方法由GetService<IFoo>()变成了GetKeyedService<IFoo>("keyA"),并且调用这两个方法创建出来的对象是不同的。

如果想通过构造函数注入,只需要在参数前面加上特性[FromKeyedServices("keyA")] 即可,特性里的参数就是key的名字。如果想在构造函数中获取key的值则使用特性[ServiceKey]。我们还可以注册时把key设置为KeyedService.AnyKey(这是框架提供的类),只需使用任意非null值 作为key就可以获取对象。暂时不支持使用通配符匹配,也许以后会加......

csharp 复制代码
class Bar : IBar
{
    public Bar([ServiceKey] int key, [FromKeyedServices("keyA")] IFoo foo, IServiceProvider root)
    {
        //注意:key的类型要和调用时一致。
        Console.WriteLine($"key:{key},Compare:{foo == root.GetKeyedService<IFoo>("keyA")}");
    }
}
public static class KeyedService
{
    /// Represents a key that matches any key.
    public static object AnyKey { get; } = new AnyKeyObj();
    private sealed class AnyKeyObj
    {
        public override string? ToString() => "*";
    }
}

深入理解

8.0之前,获取一个对象需要用到的一个"标识",比如调用GetService<IFoo>(),这个"标识"就是IFoo;也就是ServiceDescriptor里面的ServiceType。而在8.0后"标识"变成了IFoo+"keyA",也就是ServiceDescriptor里面的ServiceType+新增的ServiceKey

csharp 复制代码
public class ServiceDescriptor
{
    public object? ServiceKey { get; } 
    public Type? ImplementationType => _implementationType;        
    public Type ServiceType { get; }
    public ServiceLifetime Lifetime { get; }

对于以前注册的类,ServiceKey默认是null,所以"标识"就是ServiceKey+null。调用GetService<IFoo>()就等于调用GetKeyedService<IFoo>(null)

再举一个例子:

csharp 复制代码
//类型的注册信息放在_descriptorLookup,8.0前,是通过ServiceType作为字典的键,
//8.0是把ServiceIdentifier(也就是ServiceKey+ServiceType)作为字典的键
//7.0
private readonly Dictionary<Type, ServiceDescriptorCacheItem> _descriptorLookup = new();
//8.0
private readonly Dictionary<ServiceIdentifier, ServiceDescriptorCacheItem> _descriptorLookup = new();
internal readonly struct ServiceIdentifier : IEquatable<ServiceIdentifier>
{
    public object? ServiceKey { get; }
    public Type ServiceType { get; }
}

循环引用

前面讲到可以通过[ServiceKey]获取调用时的Key;而没有注册key的服务是无法在构造函数中注入key的值。通过这个功能可以解决循环引用的问题,先看代码。

csharp 复制代码
class Foo : IFoo
{
    //这个构造函数给GetService<IFoo>()使用
    public Foo()
    {
        this.Num = 10;
    }
    //这个构造函数给GetKeyedServices<IFoo>("keyA")使用
    public Foo([ServiceKey] string key, IFoo foo)
    {
        Console.WriteLine($"key:{key},this.Num:{this.Num},foo.Num:{foo.Num}");
    }
    public int Num { get; set; }
}

代码执行流程:

  • 1.DI首先获取Foo的所有构造函数并且按构造函数的参数从多到少进行排序
  • 2.遍历所有构造函数,首先获取参数最多的构造函数 Foo([ServiceKey] string key, IFoo foo),开始判断构造函数的参数能否被DI创建
  • 3.DI首先判断string key这个参数,能够创建;然后继续判断第二个参数IFoo foo能否被创建
  • 4.重复第一步
  • 5.重复第二步
  • 6.DI首先判断string key这个参数,不能够创建;所以无法调用构造函数 Foo([ServiceKey] string key, IFoo foo)创建Foo实例
  • 7.继续遍历构造函数,第二个构造函数是无参的,DI能够创建foo对象。
  • 8.对于GetKeyedServices<IFoo>("keyA"),是使用的这个构造函数Foo([ServiceKey] string key, IFoo foo)创建的对象。IFoo foo是使用无参构造函数创建的。

注意点:

  • 参数[ServiceKey] string key一定要写在参数IFoo foo前面,否则就会循环引用
  • 注册服务时,要注册两种(带key的和不带key的都要注册)AddScoped<IFoo, Foo>() .AddKeyedScoped<IFoo, Foo>("keyA")

总结

以前的用法往往是接口对应实现类,通过DI获取对象,只需要知道接口的名字,就可以通过GetService方法或者构造函数注入获取对象。

现在是接口+key对应实现类,通过DI获取对象,需要知道接口+key。如果key为null就和以前的用法一模一样。

结束。第一次写文章如有错误,欢迎各位批评指点,谢谢!

相关推荐
Esofar18 天前
Dddify:给 ASP.NET Core 项目一套轻量、清晰、可落地的 DDD 基础设施
c#·ddd·asp.net core·cqrs·dddify·clean architecture
CSharp精选营1 个月前
.NET 8 Web开发入门(三):解构引擎——依赖注入(DI)与中间件管道
中间件·asp.net core·依赖注入·ioc容器·请求管道·服务生命周期
医疗信息化王工2 个月前
基于ASP.NET Core的医院输血审核系统设计与实现
后端·mvc·asp.net core·输血审核
医疗信息化王工2 个月前
基于ASP.NET Core的住院日志统计系统设计与实现
后端·layui·asp.net core·npoi·dapper
无风听海2 个月前
.NET10之HttpContext.RequestServices 深入解析
.net·asp.net core
硅基喵2 个月前
聊聊 ASP.NET Core 中间件和过滤器的区别
asp.net core
小邓的技术笔记2 个月前
Serilog:从结构化日志认知到 .NET 工程落地
asp.net core·结构化日志·可观测性·serilog
硅基喵2 个月前
Serilog:从结构化日志认知到 .NET 工程落地
asp.net core·可观测性
硅基喵3 个月前
ASP.NET Core 外部依赖调用治理实战:HttpClientFactory、Polly 与幂等边界
asp.net core·架构设计
硅基喵3 个月前
ASP.NET Core 认证鉴权实战:JWT、Policy 与权限边界怎么落地
asp.net core·工程实践