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就和以前的用法一模一样。

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

相关推荐
EdisonZhou20 小时前
使用MCP C# SDK开发MCP Server + Client
llm·aigc·asp.net core·.net core
超软毛毛虫13 天前
ASP.NET Core 模型验证消息的本地化新姿势
asp.net core·localization
lixww.cn2 个月前
ASP.NET Core用MediatR实现领域事件
ddd·asp.net core·mediatr
lixww.cn2 个月前
ASP.NET Core SignalR向部分客户端发消息
javascript·websocket·vue·asp.net core·signalr
lixww.cn2 个月前
ASP.NET Core SignalR的协议协商
asp.net core·signalr
lixww.cn2 个月前
ASP.NET Core SignalR的分布式部署
redis·消息队列·asp.net core·signalr
lixww.cn2 个月前
ASP.NET Core对JWT的封装
asp.net core·jwt·authorize
lixww.cn2 个月前
ASP.NET Core JWT Version
asp.net core·jwt·filter·identity
lixww.cn2 个月前
ASP.NET Core JWT
asp.net core·jwt
lixww.cn2 个月前
ASP.NET Core标识框架Identity
asp.net core·rbac·identity