有趣的懒加载解决依赖循环的问题

最近多次遇到循环引用的问题,感觉于找到一种骚操作解决,懒加载。

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