有趣的懒加载解决依赖循环的问题
最近多次遇到循环引用的问题,感觉于找到一种骚操作解决,懒加载。
C# 中的Lazy<> 类型,只有在使用到这个值的时候才会去实例化,在此之前将会保存实例化的委托,于是可以利用这种方式解决依赖循环,当然,缺点是不能在构造函数中使用实例,否则又会进入到循环了。
一、首先创建一个接口ICircular<> 以后将会产生循环的类使用这个接口调用。
/// <summary> /// 循环引用的接口。 /// </summary> /// <typeparam name="TClass">会循环引用的类型。</typeparam> public interface ICircular<Target> where Target : class { /// <summary> /// 实例。 /// </summary> public Target Instance { get; } /// <summary> /// 生命周期类型。 /// </summary> public ServiceLifetime Lifetime { get; } }
实现方法也很简单。
public class Circular<Target> : ICircular<Target> where Target : class { private readonly Lazy<Target> _implemente; public Circular(Lazy<Target> target, ServiceLifetime lifetime) { _implemente = target; Lifetime = lifetime; } public Target Instance => _implemente.Value; public ServiceLifetime Lifetime { get; } }
为了方便注册服务,创建注册服务的接口
/// <summary> /// 循环引用的服务容器。 /// </summary> public interface ICircularServiceCollection { /// <summary> /// 添加单例。 /// </summary> /// <returns></returns> ICircularServiceCollection AddTransient<Target>() where Target : class; /// <summary> /// 添加Scoped。 /// </summary> /// <returns></returns> ICircularServiceCollection AddScoped<Target>() where Target : class; /// <summary> /// 添加Scoped、 /// </summary> /// <typeparam name="Target">目标。</typeparam> /// <typeparam name="TImplementation">实例。</typeparam> /// <returns></returns> ICircularServiceCollection AddScoped<Target, TImplementation>() where Target : class where TImplementation : class, Target; /// <summary> /// 添加单例。 /// </summary> /// <returns></returns> ICircularServiceCollection AddSingleton<Target>() where Target : class; /// <summary> /// 完成。 /// </summary> /// <returns></returns> IServiceCollection Completed(); }
实现依然很简单
public class CircularServiceCollection : ICircularServiceCollection { private readonly IServiceCollection _serviceDescriptors; /// <summary> /// 构造函数。 /// </summary> /// <param name="serviceDescriptors">注入容器。</param> public CircularServiceCollection(IServiceCollection serviceDescriptors) { _serviceDescriptors = serviceDescriptors; } public ICircularServiceCollection AddScoped<Target>() where Target : class { _serviceDescriptors.AddScoped<Target>(); _serviceDescriptors.AddScoped(ImplementationAction<Target>(ServiceLifetime.Scoped)); return this; } public ICircularServiceCollection AddScoped<Target, TImplementation>() where Target : class where TImplementation : class, Target { _serviceDescriptors.AddScoped<Target, TImplementation>(); _serviceDescriptors.AddScoped(ImplementationAction<Target>(ServiceLifetime.Scoped)); return this; } public ICircularServiceCollection AddSingleton<Target>() where Target : class { _serviceDescriptors.AddSingleton<Target>(); _serviceDescriptors.AddSingleton(ImplementationAction<Target>(ServiceLifetime.Singleton)); return this; } public ICircularServiceCollection AddTransient<Target>() where Target : class { _serviceDescriptors.AddTransient<Target>(); _serviceDescriptors.AddTransient(ImplementationAction<Target>(ServiceLifetime.Transient)); return this; } public IServiceCollection Completed() { return _serviceDescriptors; } private Func<IServiceProvider, ICircular<Target>> ImplementationAction<Target>(ServiceLifetime lifetime) where Target : class { return (serviceProvider) => { return new Circular<Target>(new Lazy<Target>(() => serviceProvider.GetService<Target>()), lifetime); }; } }
在服务容器中使用
/// <summary> /// 重复引用选项。 /// </summary> public static ICircularServiceCollection AddCircularOptions(this IServiceCollection serviceDescriptors) { return new CircularServiceCollection(serviceDescriptors); }
测试一下:下面代码中,原本A构造函数需要注入B,B构造函数需要注入A,将其改成ICircular<B>这种形式
internal class A : BaseClass { private readonly ICircular<B> _b; public A(ICircular<B> b) { _b = b; } public B B { get => _b.Instance; } } internal class B : BaseClass { private readonly ICircular<A> _a; public B(ICircular<A> a) { _a = a; } public A A { get => _a.Instance; } }
[Fact] public void Test_AReferenceB_Should_B() { var serviceProvider = new ServiceCollection() .AddCircularOptions() .AddScoped<A>() .AddTransient<B>() .Completed().BuildServiceProvider(); var a = serviceProvider.GetRequiredService<A>(); var b = serviceProvider.GetRequiredService<ICircular<B>>(); var c = serviceProvider.GetRequiredService<B>(); Assert.Equal(ServiceLifetime.Transient, b.Lifetime); Assert.Equal("B", a.B.Name); Assert.Equal("A", c.A.Name); }
这样就可以解决依赖循环的问题了。
然而这并没有什么卵用,循环引用本来就应该分离业务,而不是用这种花里胡巧的方式解决。另外排除java的东西咱们.net不用的心理,难道字段属性注入它不香吗?
https://github.com/yeqifeng2288/BoringThings/tree/master/src/DeCircular