美文网首页.NET
.NET Core 依赖注入改造(2)- 委托转换

.NET Core 依赖注入改造(2)- 委托转换

作者: 冰麟轻武 | 来源:发表于2018-08-07 14:39 被阅读108次

    .NET Core 依赖注入改造(1)- 命名服务
    .NET Core 依赖注入改造(2)- 委托转换
    .NET Core 依赖注入改造(3)- ILogger
    .NET Core 依赖注入改造(4)- ActivatorUtilities
    .NET Core 依赖注入改造(5)- Context

    .NET Core 依赖注入改造(附1)- Autowrite

    一、

    上一篇 写到了命名服务
    演示了使用命名服务+委托的方式来获取服务,
    但总觉得还有一些不不爽,
    为什么委托就必须使完全一样的委托才能获取服务?
    为什么不能直接注入方法?

    var provider = new ServiceCollection()
            .AddSingleton<Func<object, string>>(o => JsonConvert.SerializeObject(o)) 
            .BuildServiceProvdier();
    //........
    delegate string ToJsonString(object obj);
    var toJsonStriong = provider.GetNamedService<ToJsonString>("ToJsonString");
    
    var method = typeof(JsonConvert).GetMethod("SerializeObject", new[] { typeof(object) });
    var provider = new ServiceCollection()
            .AddNamedSingleton("ToJsonString", method) //注入方法
            .BuildServiceProvdier();
    //........
    delegate string ToJsonString(object obj);
    var toJsonStriong = provider.GetNamedService<ToJsonString>("ToJsonString");
    

    所以我准备继续改造一下

    二、

    经过上一次对源码的探索,整理了官方DI框架大致的执行逻辑


    现在有2个需要解决的问题

    1. 做委托类型转换
      这个只能在最后一步GetService时,先获取原来注入的服务,然后根据新的Type进行类型转换;
      这里只要替换掉原来的 ServiceProvider 就可以了;
    2. 在不知道原类型的情况下无法获取原注入的服务(命名服务会简单一些)
      所以我可以将所有注入的委托服务都转换为MethodInfo类型注入,
      这个可以有两种方式干涉
      a. 重新实现 ServiceCollectionAdd 操作中替换
      b. 在编译BuildServiceProvdier之前做,循环所有注入的服务,并替换其中的委托
      a方案代价太大,而且在使用上也有限制,所以我选择了b方案

    最终我使用了一个新的扩展方法BuildSupportDelegateServiceProvdier来代替BuildServiceProvdier

    改造后的逻辑大概是这样的

    三、

    扩展方法 BuildSupportDelegateServiceProvdier

    public static class DelegateServiceProvdierExtensions
    {
        /// <summary>
        /// 构造支持委托转换的服务提供程序
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceProvider BuildSupportDelegateServiceProvdier(this IServiceCollection services)
        {
            var methodServices = new List<ServiceDescriptor>();
            // 循环所有服务
            foreach (var item in services)
            {
                if (typeof(Delegate).IsAssignableFrom(item.ServiceType))
                {
                    // 针对委托类型的服务做一次MethodInfo处理
                    if (item.ImplementationInstance is Delegate)
                    {
                        methodServices.Add(new ServiceDescriptor(typeof(MethodInfo), ((Delegate)item.ImplementationInstance).Method));
                    }
                    else if (item.ImplementationFactory != null)
                    {
                        methodServices.Add(new ServiceDescriptor(typeof(MethodInfo), p => ((Delegate)item.ImplementationFactory(p)).Method, item.Lifetime));
                    }
                }
            }
            methodServices.ForEach(services.Add); //注入MethodInfo服务
            var provider = services.BuildServiceProvider();
            return new DelegateServiceProvdier(provider); // 返回装饰类
        }
    }
    

    DelegateServiceProvdier 装饰类

    /// <summary>
    /// 可创建委托服务的提供程序
    /// </summary>
    internal class DelegateServiceProvdier : IServiceProvider
    {
        readonly IServiceProvider _provider;
        readonly ILogger _logger;
        readonly ConcurrentDictionary<Type, object> _services;
    
        /// <summary>
        /// 构造一个服务提供程序的代理
        /// </summary>
        /// <param name="provider">被代理的服务提供程序</param>
        public DelegateServiceProvdier(IServiceProvider provider)
        {
            _provider = provider ?? throw new ArgumentNullException(nameof(provider));
            _logger = _provider.GetService<ILoggerFactory>()?.CreateLogger<DelegateServiceProvdier>();
            _services = new ConcurrentDictionary<Type, object>(TypeComparer.Instance);
        }
    
        /// <summary>
        /// 获取指定类型的服务
        /// </summary>
        /// <param name="serviceType">服务类型</param>
        /// <returns></returns>
        public object GetService(Type serviceType)
        {
            if (typeof(IServiceProvider) == serviceType)
            {
                return this;
            }
            // 从 _provider 中获取服务
            var service = _provider.GetService(serviceType);
    
    
            if (service == null)
            {
                // 当常规方式没有获取到服务,且服务是委托类型时,尝试获取MethodInfo服务,并返回最后一个签名相同的MethodInfo并转换为指定类型的委托
                return typeof(Delegate).IsAssignableFrom(serviceType)
                        ? _services.GetOrAdd(serviceType, x => ConvertDelegate(x, _provider.GetServices<MethodInfo>()))
                        : null;
            }
    
            if (service is Delegate delegateService)
            {
                // 当获取的服务是委托,但与要求的类型不符时,尝试转换委托类型
                if (serviceType is IServiceProvider tp
                    && tp.GetService(typeof(Type)) is Type delegateType
                    && typeof(Delegate).IsAssignableFrom(delegateType)
                    && !delegateType.IsInstanceOfType(service))
                {
                    return _services.GetOrAdd(serviceType, x => ConvertDelegate(delegateType, new[] { delegateService.Method }));
                }
                return service;
            }
    
            if (service is IEnumerable enumerable && serviceType.IsGenericType && serviceType.GetGenericArguments().Length == 1)
            {
                // 当获取的服务是泛型集合时
                var type = serviceType.GetGenericArguments()[0];
                if (type is IServiceProvider tp && tp.GetService(typeof(Type)) is Type delegateType)
                {
                    return _services.GetOrAdd(serviceType, x => ConvertDelegates(delegateType, enumerable));
                }
                else
                {
                    return _services.GetOrAdd(serviceType, x => ConvertDelegates(type, _provider.GetServices<MethodInfo>()));
                }
            }
            return service;
        }
    
        /// <summary>
        /// 转换委托服务集合
        /// </summary>
        /// <param name="delegateType"></param>
        /// <param name="enumerable"></param>
        /// <returns></returns>
        private IEnumerable ConvertDelegates(Type delegateType, IEnumerable enumerable)
        {
            var newServices = new ArrayList();
            var delegateMethod = delegateType.GetMethod("Invoke");
            foreach (var item in enumerable)
            {
                if (delegateType.IsInstanceOfType(item))
                {
                    newServices.Add(item);
                    continue;
                }
                var method = (item as Delegate)?.Method ?? item as MethodInfo;
                if (CompareMethodSignature(delegateMethod, method))
                {
                    newServices.Add(method.CreateDelegate(delegateType, null));
                }
            }
            return newServices.ToArray(delegateType);
        }
    
        /// <summary>
        /// 转换委托服务
        /// </summary>
        private object ConvertDelegate(Type delegateType, IEnumerable<MethodInfo> methods)
        {
            var delegateName = delegateType.Name;
            var delegateMethod = delegateType.GetMethod("Invoke");
            MethodInfo last = null;
            MethodInfo lastExact = null;
            foreach (var method in methods)
            {
                if (CompareMethodSignature(method, delegateMethod))
                {
                    if (method.Name == delegateName)
                    {
                        lastExact = method;
                    }
                    else if (lastExact == null)
                    {
                        last = method;
                    }
                }
            }
            try
            {
                return (lastExact ?? last).CreateDelegate(delegateType, null);
            }
            catch (Exception ex)
            {
                _logger?.LogError(ex, ex.Message);
                return null;
            }
        }
    
    
        /// <summary>
        /// 比较2个方法签名是否相同
        /// </summary>
        /// <param name="method1">方法1</param>
        /// <param name="method2">方法2</param>
        /// <returns></returns>
        private bool CompareMethodSignature(MethodInfo method1, MethodInfo method2)
        {
            if (method1 == null || method2 == null || method1.ReturnType != method2.ReturnType)
            {
                return false;
            }
            var p1 = method1.GetParameters();
            var p2 = method2.GetParameters();
            if (p1.Length != p2.Length)
            {
                return false;
            }
            for (var i = 0; i < p1.Length; i++)
            {
                if (p1[i].ParameterType != p2[i].ParameterType)
                {
                    return false;
                }
            }
            return true;
        }
    }
    

    四、

    这里有一个坑,需要单开一节来说下

    new ConcurrentDictionary<Type, object>(TypeComparer.Instance);
    

    之前代码中的 TypeComparer.Instance 其实是一个自定义实现

    原因是

    在获取服务集合时(provider.GetServices<ToJsonString>),的一个特殊对象TypeBuilderInstantiation

    public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType)
    {
        var genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType);
        return (IEnumerable<object>)provider.GetRequiredService(genericEnumerable);
    }
    

    问题就出在 typeof(IEnumerable<>).MakeGenericType(serviceType); 这个操作上

    仔细看t1t2的类型是不同的,关键点在于NamedType并非系统RuntimeType类型的对象


    这里的代码,每次都会new一个新的TypeBuilderInstantiation,如果不重新实现IEqualityComparer<Type>,使用默认方式比较,即使2个属性完全相同的TypeBuilderInstantiation也无法得到相同的HashCode,将造成服务缓存无法正常工作

    internal class TypeComparer : IEqualityComparer<Type>
    {
        public static readonly TypeComparer Instance = new TypeComparer();
        private static readonly Type _runtimeType = typeof(int).GetType();
        public bool Equals(Type x, Type y)
        {
            if (x == null || y == null)
            {
                return x.Equals(y);
            }
            if ((x.GetType() != _runtimeType && x.IsGenericType) || (y.GetType() != _runtimeType && y.IsGenericType))
            {
                if (!Equals(x.GetGenericTypeDefinition(), y.GetGenericTypeDefinition()))
                {
                    return false;
                }
                if (x.IsGenericTypeDefinition || y.IsGenericTypeDefinition)
                {
                    return x.IsGenericTypeDefinition == y.IsGenericTypeDefinition;
                }
                var args1 = x.GetGenericArguments();
                var args2 = y.GetGenericArguments();
                if (args1.Length != args2.Length)
                {
                    return false;
                }
                for (var i = 0; i < args1.Length; i++)
                {
                    if (!Equals(args1[i], args2[i]))
                    {
                        return false;
                    }
                }
                return true;
            }
            return x.Equals(y);
        }
    
        public int GetHashCode(Type obj)
        {
            if (obj != null && obj.GetType() != _runtimeType && obj.IsGenericType)
            {
                var hashcode = obj.GetGenericTypeDefinition().GetHashCode();
                if (!obj.IsGenericTypeDefinition)
                {
                    foreach (var item in obj.GetGenericArguments())
                    {
                        hashcode ^= item.GetHashCode();
                    }
                }
                return hashcode;
            }
            return obj?.GetHashCode() ?? 0;
        }
    }
    

    五、

    最后项目结构是这样的



    测试一下


    据说,依赖注入时命名服务委托转换更配哦。。。

    delegate string ToJsonString(object obj);
    delegate string ToXmlString(object obj);
    static void Main(string[] args)
    {
        var provider = new ServiceCollection()
                             .AddNamedSingleton<Func<object, string>>("ToJsonString", o => JsonConvert.SerializeObject(o))
                             .AddNamedSingleton<Func<object, string>>("ToXmlString", o => o.ToXml().ToString())
                            .BuildSupportDelegateServiceProvdier();
    
        var x = new
        {
            id = 1,
            name = "blqw"
        };
        var toJsonStriong = provider.GetNamedService<ToJsonString>("ToJsonString");
        Console.WriteLine(toJsonStriong(x));
        var toXmlString = provider.GetNamedService<ToXmlString>("ToXmlString");
        Console.WriteLine(toXmlString(x));
    
        Business.Operation(provider);
    }
    

    六、

    github:https://github.com/blqw/blqw.DI/tree/master/src/blqw.DelegateServiceProvdier
    nuget:https://www.nuget.org/packages/blqw.DI.DelegateServiceProvdier

    相关文章

      网友评论

      本文标题:.NET Core 依赖注入改造(2)- 委托转换

      本文链接:https://www.haomeiwen.com/subject/xgzvvftx.html