最新文章 (全部类别)
VS2022消除编译警告
“SymmetricAlgorithm.Create(string)”已过时:“Cryptographic factory methods accepting an algorithm name are obsolete. Use the parameterless Create factory method on the algorithm type instead
SHA256Managed/SHA512Managed已过时:Derived cryptographic types are obsolete. Use the Create method on the base type instead
MD5CryptoServiceProvider已过时:Derived cryptographic types are obsolete. Use the Create method on the base type instead
C#使用HttpClient获取IP地址位置和网络信息
判断IP是否是外网IP、内网IP
C#使用HttpClient获取公网IP
WebRequest.Create(string)已过时:WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead
C#根据第三方提供的IP查询服务获取公网外网IP地址
html/dom/js/javascript开发记录
调试ASP.NETCore Web站点 - 清理IISExpress缓存数据(js,css)
EFCore+Oracle根据不同的Schema连接数据库
主程序集成CSFramework.EF 数据库框架(.NET7版本)
CSFramework.EF数据库框架简介(.NET8+EFCore)
迁移ECS服务器:导致ORACLE监听服务启动不了解决方案
SQLite数据库
VS2022编译报错:Visual Studio 容器工具需要 Docker Desktop
.NET 9 预览版+C#13新功能
EFCore禁用实体跟踪
WebApi开发框架V3.0 (.NETCore+EFCore) 增加AppSettings全局参数类
C#获取应用程序所有依赖的程序集
LINQ Expression 多条件复合条件组合(And/Or)
CSFrameworkV6客户案例 - MHR - 宁德时代制造人力资源系统
CS软件授权注册系统V3 - 发布证书
C/S软件授权注册系统V3.0(Winform+WebApi+.NET8+EFCore版本)
CS软件授权注册系统V3 - 购买方式
CS软件授权注册系统V3 - 试用版下载
CS软件授权注册系统-客户登记(制作证书)
C/S软件授权注册系统V3.0 - 管理员工具
CSFrameworkV6旗舰版开发框架 - 集成软件授权认证系统
CSFramework.Authentication 软件证书管理系统 - 制作软件客户授权证书
CSFramework.Authentication 软件证书管理系统 - MAC地址管理
CSFramework.Authentication 软件授权证书管理系统
Login/Logout接口调用dalUser的Login/Logout方法
C# Newtonsoft.Json.Linq.JObject 转对象
CSFramework.Authentication 软件授权认证系统 - 软件测试报告
C/S架构软件开发平台 - 旗舰版V6.0 - 底层框架迭代开发
C/S架构软件开发平台 - 旗舰版V6.1新功能 - 增加软件授权认证模块
C/S架构软件开发平台 - 旗舰版CSFrameworkV6 Bug修改记录
CS软件授权注册系统V3 - 开发手册 - 软件集成与用户注册
CS软件授权注册系统-模拟MES/ERP用户注册软件
CS软件授权注册系统-发布/部署WebApi服务器(IIS+.NET8+ASP.NETCore)
CS软件授权注册系统-VS2022调试WebApi接口
.NETCore Console控制台程序使用ILogger日志
CS软件授权注册系统-WebApi服务器介绍
ASP.NETCore集成Swagger添加Authorize按钮Bearer授权
CS软件授权注册系统-WebApi服务器配置
.NETCore WebApi发布到IIS服务器无法打开swagger
.NET8/ .NETCore /ASP.NETCore 部署WebApi到IIS服务器需要安装的运行环境
HTTP Error 500.31 - Failed to load ASP.NET Core runtime,Microsoft.NetCore.App or Microsoft.AspNetCore.App was not found.
.net敏捷开发,创造卓越

IOC实践(IOC控制反转)


一、IOC

1.什么是IOC?

控制反转(英语:Inversion of Control,缩写为IoC),是[面向对象编程]中的一种设计原则,可以用来减低计算机代码之间的[耦合度]其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup).

IoC:是一种设计模式

DI:是践行控制反转思想的一种方式

2.为什么要用IOC

因为IoC 控制反转是依赖抽象,而抽象是稳定的,不依赖细节,因为细节还可能会依赖其他细节,为了屏蔽细节,需要使用依赖注入去解决无限层级的对象依赖。

3.Net中常用的IoC容器

目前用的最多的是AutoFac和Castle,在.Net Core中框架内置了IOC容器,Unity和ObjectBuilder是相对比较久远的框架,用的比较少。

  1. AutoFac

  2. Castle

  3. Unity

  4. ObjectBuilder

二、如何手写实现?

1.基本设计

核心思想: 工厂 + 反射

首先我们想自己实现一个IoC容器其实并不难,我们在使用现有的IoC容器都知道,在使用前,我们需要先注册,然后才能使用

所以我们将工厂换成手动注册的方式,因为写一大堆if else 或者switch也不太美观,根据主流IoC的使用方式来以葫芦画瓢,如果后期继续完善功能加入程序集注入的话,还是得实现一个工厂,来省略手动注册

但是这次目标是实现一个简易版的IoC容器,我们先实现基础功能,待后面一步一步去完善,再加入一些新的功能,即我们不考虑性能或者扩展度,目的是循序渐进,在写之前我们先整理出 实现步骤和实现方式

  1. 方便接入和扩展,我们在这先定义一个容器接口 IManualContainer

  2. 定义ManualContainer继承实现IManualContainer

  3. 声明一个静态字典对象存储注册的对象

  4. 利用反射构建对象,考虑到性能可以加入Expression或者Emit的方式来做一些优化

IOC实践(IOC控制反转)
 
public interface IManualContainer
{
      void Register<TFrom, To>(string servicesName = null) where To : TFrom;

      Tinterface Resolve<Tinterface>(string servicesName = null);
}
2.要实现的功能

1.基本对象构造

2.构造函数注入

3.多级依赖和多构造函数及自定义注入

4.属性注入&方法注入

5.单接口多实现

三、编码实现及思路剖析

1.实现构造对象(单接口注入)

1.首先实现接口来进行编码私有字段 container用来存储注册的类型,key是对应接口的完整名称,Value是需要Resolve的类型。

2.泛型约束保证需要被Resolve类型 (To) 实现或者继承自注册类型 (TFrom)

public class ManualContainer : IManualContainer
{
   //存储注册类型
   private static Dictionary<string, Type> container = 
   new Dictionary<string, Type>();
   
   //注册
    public void Register<TFrom, To>(string servicesName = null) where To : TFrom
    {
        string Key = $"{typeof(TFrom).FullName}{servicesName}";
        if (!container.ContainsKey(Key))
        {
            container.Add(Key, typeof(To));
        }
    }

1.实现构造对象,首先需要传入被构造的类型的抽象接口T

2.在Resolve中根据T作为Key,在存储容器中找到注册时映射的类型,并通过反射构造对象

   //构建对象
   public TFrom Resolve<TFrom>(string servicesName = null)
   {
       string Key = $"{typeof(TFrom).FullName}{servicesName}";
       container.TryGetValue(key, out Type target);
       if(target is null)
       {
           return default(TFrom);
       }
       object t = Activator.CreateInstance(target);
   }
}

1.首先我们准备需要的接口(ITestA)和实例(TestA)来利用容器来构造对象

public interface ITestA
{
    void Run();
}
public class TestA : ITestA
{
   public void Run()=> Console.WriteLine("这是接口ITestA的实现");
}

2.调用IoC容器来创建对象

IManualContainer container = new ManualContainer();
//注册到容器中
container.Register<ITestA, TestA>();
ITestA instance = container.Resolve<ITestA>();
instance.Run();
//out put "这是接口ITestA的实现"
2.构造函数注入

1.假设我们的TestA类中需要ITestB接口的实例或者其他更多类型的实例,并且需要通过构造函数注入,我们应该如何去完善我们的IoC容器呢?

public class TestA : ITestA
{ 
   private ITestB testB = null;
   //构造函数
   public TestA(ITestB testB)=> this.testB = testB;
   
   public void Run()
   {
      this.testB.Run();
      Console.WriteLine("这是接口ITestA的实现");
   }
}

2.我们按照上面的步骤照常注册和构造对象,发现报错了,在Resolve()的时候,经过调试知道是使用反射构造的时候报错了,因为在构造TestA缺少构造参数,那么我们就需要在反射构造时加入参数。

  1. 先定义List<object>集合存储对象构造时需要的参数列表

  2. 通过需要被实例的目标类型找到类中的构造函数,暂不考虑多构造函数case

  3. 找到构造函数参数及类型,然后创建参数的实例加入List中,在反射构造时传入参数就解决了

   //完善Resolve构建对象函数
   public TFrom Resolve<TFrom>(string servicesName = null)
   {
       string Key = $"{typeof(TFrom).FullName}{servicesName}";
       container.TryGetValue(key, out Type target);
       if(target is null)
       {
           return default(TFrom);
       }
       
       //存储参数列表
       List<object> paramList = new List<object>();
       //找到目标类型的构造函数,暂不考虑多构造函数case
       var ctor = target.GetConstructors().FirstOrDefault();
       //找到参数列表
       var ctorParams = ctor.GetParameters();
       foreach (var item in ctorParams)
       {
           //参数类型
           Type paramType = item.ParameterType;
           string paramKey = paramType.FullName;
           //找到参数注册时映射的实例
           container.TryGetValue(paramKey, out Type ParamType);
           //构造出实例然后加入参数列表
           paramList.Add(Activator.CreateInstance(ParamType));
       }     
       object t = Activator.CreateInstance(target,paramList);
   }
}
3.多级依赖(递归)

根据上面我们目前实现的结果来看,这是解决了构造函数和多参数注入以及基本的构造对象问题,那现在问题又来了

  1. 如果是很多层的依赖该怎么办?

  2. 例如多个构造函数怎么办呢?

  3. 在多个构造函数中用户想自定义需要被注入的构造函数怎么办?

总结3点问题

  • 1.多级依赖问题

例如ITestB 的实例中依赖ITestC,一直无限依赖我们怎么解决呢?毫无疑问,做同样的事情,但是要无限做下去,就使用 递归,下面我们来改造我们的方法

  • 2.多个构造函数

    1. 取参数最多的方式注入 (AutoFac)

    2. 取并集方式注入 (ASP .NET Core)

  • 3.自定义注入

    我们可以使用特性标记的方式来实现,用户在需要被选择注入的构造函数上加入特性标签来完成


1.创建私有递归方法,这个方法的作用就是创建对象用

private object CreateInstance(Type type,string serviceName = null)
{
}

2.我们选择第一种方式实现,修改之前获取第一个构造函数的代码,选择最多参数注入

 //找到目标类型的构造函数,找参数最多的构造函数
  ConstructorInfo ctor = null;
  var ctors = target.GetConstructors();
  ctor = ctors.OrderByDescending(x => x.GetParameters().Length).First();

3.自定义特性CtorInjection,可以使用户自定义选择

 //自定义构造函数注入标记
 [AttributeUsage(AttributeTargets.Constructor)]
 public class CtorInjectionAttribute : Attribute
 {
 }

4.最终代码

private object CreateInstance(Type type,string serviceName = null)
{
   string key = $"{ type.FullName }{serviceName}";
   container.TryGetValue(key, out Type target);
   //存储参数列表
   List<object> paramList = new List<object>();
  
   ConstructorInfo ctor = null;
   //找到被特性标记的构造函数作为注入目标
   ctor = target.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(CtorInjectionAttribute), true));

   //如果没有被特性标记,那就取构造函数参数最多的作为注入目标
   if (ctor is null)
   {
      ctor = target.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First();
   }
   //找到参数列表
   var ctorParams = ctor.GetParameters();
   foreach (var item in ctorParams)
   {
       //参数类型
       Type paramType = item.ParameterType;
       //递归调用构建对象
       object paramInstance = CreateInstance(paramType);
       //构造出实例然后加入参数列表
       paramList.Add(paramInstance);
   }    
   object t = Activator.CreateInstance(target);
   return t;
}

 public TFrom Resolve<TFrom>() 
 {
    return (TFrom)this.CreateInstance(typeof(TFrom));
 }
4.属性注入&方法注入

1.自定义特性PropInjection,可以使用户自定义选择

 //自定义构造属性注入标记
 [AttributeUsage(AttributeTargets.Property)]
 public class PropInjectionAttribute : Attribute
 {
 }
  //自定义构造方法注入标记
 [AttributeUsage(AttributeTargets.Method)]
 public class MethodInjectionAttribute : Attribute
 {
 }

2.遍历实例中被标记特性的属性。

3.获取属性的类型,调用递归构造对象函数。

4.设置目标对象的属性值。

5.方法注入也是同理,换汤不换药而已

private object CreateInstance(Type type,string serviceName = null)
{
  string key = $"{ type.FullName }{serviceName}";
   container.TryGetValue(key, out Type target);
   //存储参数列表
   List<object> paramList = new List<object>();
  
   ConstructorInfo ctor = null;
   //找到被特性标记的构造函数作为注入目标
   ctor = target.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(CtorInjectionAttribute), true));

   //如果没有被特性标记,那就取构造函数参数最多的作为注入目标
   if (ctor is null)
   {
      ctor = target.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First();
   }
   //找到参数列表
   var ctorParams = ctor.GetParameters();
   foreach (var item in ctorParams)
   {
       //参数类型
       Type paramType = item.ParameterType;
       //递归调用构建对象
       object paramInstance = CreateInstance(paramType);
       //构造出实例然后加入参数列表
       paramList.Add(paramInstance);
   }    
   object t = Activator.CreateInstance(target);
   
   //获取目标类型的被特性标记的属性<属性注入>
   var propetys = target.GetProperties().Where(x => x.IsDefined(typeof(PropInjectionAttribute), true));
   foreach (var item in propetys) 
   {
       //获取属性类型
       Type propType = item.PropertyType;
       object obj = this.CreateInstance(propType);
       //设置值
       item.SetValue(t, obj);
   }
   
   //获取目标类型的被特性标记的方法<方法注入>
   var methods = target.GetMethods().Where(x => x.IsDefined(typeof(MethodInjectionAttribute), true)).ToList();
   foreach (var item in methods)
    {
        List<object> methodParams = new List<object>();
        foreach (var p in item.GetParameters())
        { 
            //获取方法参数类型
            Type propType = p.ParameterType;
            object obj = this.CreateInstance(propType);
            methodParams.Add(obj);
        }
        item.Invoke(t, methodParams);
    }
   return t;
}

 public TFrom Resolve<TFrom>() 
 {
    return (TFrom)this.CreateInstance(typeof(TFrom));
 }
5.单接口多实现

假设我们一个接口被多个实例实现,我们需要注入怎么操作呢?

毫无疑问我们应该想到在注入时取一个别名,没错正是这种方法,但是也会存在一个问题,我们取了别名之后只能解决注入的场景?那依赖接口的地方如何知道注入哪一个实例呢?

1.注册单接口多实例

container.Register<ITest, test1>("test1");
container.Register<ITest, test2>("test2");
ITest instance = container.Resolve<ITest>("test1");
ITest instance1 = container.Resolve<ITest>("test2");

2.创建标记特性,用来标记目标对象

[AttributeUsage(AttributeTargets.Parameter| AttributeTargets.Property)]
public class ParamterInjectAttribute : Attribute
{
   public ParamterInjectAttribute(string nickName) => NickName = nickName;
   public string NickName { get; private set; }
}

3.我们需要在依赖“单接口多实例的类中”使用时告诉参数,我们需要的实例,依然使用参数特性标记

public class use : IUse
{
    private ITest _Test = null;
    //告诉构造函数依赖ITest接口时使用别名为test1的实例
    public BLL1([ParamterInjectAttribute("test1")] ITest接口时使用别名为test1的实例 Test)
    {
        this._Test = Test;
    }
}

4.在构造对象时查找是否被特性标记,然后构造对象

foreach (var item in ctor.GetParameters())
{
    Type paramType = item.ParameterType;
    string nickName = string.Empty;
    //查找构造函数的参数是否被特性标记
    if (item.IsDefined(typeof(ParamterInjectAttribute), true))
    {
    //找到被标记需要构造的实例别名
        nickName = item.GetCustomAttribute<ParamterInjectAttribute>().NickName;
    }
    //根据别名创建对象
    object instance = CreateInstance(paramType,nickName);
    if (instance != null)
    {
        paramList.Add(instance);
    }
 }

四、总结

目前还不是很完善,只是实现了属性,方法,以及构造函数注入,很多必要功能还没有,下一步将在现有代码基础上利用Emit的方式来创建对象,加入基本的验证环节以提高健壮性,加入生命周期管理,和AOP扩展。

第一版最终代码

 public class ManualContainer : IManualContainer
 {
        private static Dictionary<string, Type> container = new Dictionary<string, Type>();
        public void Register<TFrom, To>(string servicesName = null) where To : TFrom
        {
            string Key = $"{typeof(TFrom).FullName}{servicesName}";
            if (!container.ContainsKey(Key))
            {
                container.Add(Key, typeof(To));
            }
        }

        public Tinterface Resolve<Tinterface>(string serviceName = null)
        {
            return (Tinterface)this.CreateInstance(typeof(Tinterface), serviceName);
        }

        private object CreateInstance(Type type, string serviceName = null)
        {
            string key = $"{ type.FullName }{serviceName}";
            container.TryGetValue(key, out Type target);
            object instance = ctorInjection(target);
            propInjection(target, instance);
            methodInjection(target, instance);
            return instance;
        }


        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="target">需要被创建的类型</param>
        /// <returns>被创建的实例</returns>
        private object ctorInjection(Type targetType)
        {
            List<object> paramList = new List<object>();
            ConstructorInfo ctor = null;
            ctor = targetType.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(CtorInjectionAttribute), true));
            if (ctor is null)
            {
                ctor = targetType.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First();
            }
            foreach (var item in ctor.GetParameters())
            {
                Type paramType = item.ParameterType;
                string nickName = GetNickNameByAttribute(item);
                object instance = CreateInstance(paramType, nickName);
                if (instance != null)
                {
                    paramList.Add(instance);
                }
            }
            object t = Activator.CreateInstance(targetType, paramList.ToArray());
            return t;
        }


        /// <summary>
        /// 属性注入
        /// </summary>
        /// <param name="targetType">需要被创建的类型</param>
        /// <param name="sourceType">根据需要被创建的类型构造出的实例</param>
        private void propInjection(Type targetType, object inststance)
        {
            var propetys = targetType.GetProperties().Where(x => x.IsDefined(typeof(PropInjectionAttribute), true));
            foreach (var item in propetys)
            {
                Type propType = item.PropertyType;
                string nickName = GetNickNameByAttribute(item);
                object obj = this.CreateInstance(propType, nickName);
                item.SetValue(inststance, obj);
            }
        }

        /// <summary>
        /// 方法注入
        /// </summary>
        /// <param name="targetType">需要被创建的类型</param>
        /// <param name="sourceType">根据需要被创建的类型构造出的实例</param>
        private void methodInjection(Type targetType, object inststance)
        {
            List<object> methodParams = new List<object>();
            var methods = targetType.GetMethods().Where(x => x.IsDefined(typeof(MethodInjectionAttribute), true)).ToList();
            foreach (var item in methods)
            {
                foreach (var p in item.GetParameters())
                {
                    Type propType = p.ParameterType;
                    string nickName = GetNickNameByAttribute(item);
                    object obj = this.CreateInstance(propType, nickName);
                    methodParams.Add(obj);
                }
                item.Invoke(inststance, methodParams.ToArray());
            }
        }
        
        private string GetNickNameByAttribute(ICustomAttributeProvider provider)
        {
            if (provider.IsDefined(typeof(ParamterInjectAttribute), true))
            {
                ParamterInjectAttribute attribute = provider.GetCustomAttributes(typeof(ParamterInjectAttribute), true)[0] as ParamterInjectAttribute;
                return attribute.NickName;
            }
            return string.Empty;
        }
  }

版权声明:本文为开发框架文库发布内容,转载请附上原文出处连接
C/S框架网
上一篇:ASP.NET Core中使用滑动窗口限流
下一篇:.Net Core SignalR简介-用SignalR撸个游戏
评论列表

发表评论

评论内容
昵称:
关联文章

IOC实践(IOC控制)
CSFramework.WebApiV3.依赖注入 (DI) 与 控制(IOC
CSFramework.WebApiV3.依赖注入 (DI) 与 控制(IOC
C#代码混淆及编译()
Vue指令大全 - 开发实践
ACTIVE OBJECT 模式()
[帖]ACTIVE OBJECT 模式
[帖]FreeTextBox添加自定义按钮
使用ILSpy高级编译工具完美导出源码
审核/审核jpg png PSD文件下载
C#代码混淆及编译
达梦数据库.NETCore.NET8实践指南|C/S软件开发框架
CSFrameworkV6旗舰版-重写 DoApprovalUndo方法(审核)
SQL死锁的分析与解决()
浅谈系统框架与开发模式 []
推荐C#.Net逆向编译四大软件工具
C#DataTable(List /JSON/字典 互)
如何删除Toolbar的自定义按钮? 如审核|审|根据按钮名称删除
CSFrameworkV6.0开发指南 - 保存资料后立即审核/审核后立即修改
通过射,调用DLL程序集某个类的静态方法打开窗体

热门标签
软件著作权登记证书 .NET .NET Reactor .NET5 .NET6 .NET7 .NET8 .NET9 .NETFramework APP AspNetCore AuthV3 Auth-软件授权注册系统 Axios B/S B/S开发框架 B/S框架 BSFramework Bug Bug记录 C#加密解密 C#源码 C/S CHATGPT CMS系统 CodeGenerator CSFramework.DB CSFramework.EF CSFramework.License CSFrameworkV1学习版 CSFrameworkV2标准版 CSFrameworkV3高级版 CSFrameworkV4企业版 CSFrameworkV5旗舰版 CSFrameworkV6.0 CSFrameworkV6.1 CSFrameworkV6旗舰版 DAL数据访问层 Database datalock DbFramework Demo教学 Demo实例 Demo下载 DevExpress教程 Docker Desktop DOM ECS服务器 EFCore EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR IDatabase IIS JavaScript LINQ MES MiniFramework MIS MySql NavBarControl NETCore Node.JS NPM OMS Oracle资料 ORM PaaS POS Promise API PSD RedGet Redis RSA SAP Schema SEO SEO文章 SQL SQLConnector SQLite SqlServer Swagger TMS系统 Token令牌 VS2022 VSCode VS升级 VUE WCF WebApi WebApi NETCore WebApi框架 WEB开发框架 Windows服务 Winform 开发框架 Winform 开发平台 WinFramework Workflow工作流 Workflow流程引擎 XtraReport 安装环境 版本区别 报表 备份还原 踩坑日记 操作手册 达梦数据库 代码生成器 迭代开发记录 功能介绍 国际化 基础资料窗体 架构设计 角色权限 开发sce 开发工具 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 框架升级 毛衫行业ERP 秘钥 密钥 权限设计 软件报价 软件测试报告 软件加壳 软件简介 软件开发框架 软件开发平台 软件开发文档 软件授权 软件授权注册系统 软件体系架构 软件下载 软件著作权登记证书 软著证书 三层架构 设计模式 生成代码 实用小技巧 视频下载 收钱音箱 数据锁 数据同步 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 详细设计说明书 新功能 信创 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序
联系我们
联系电话:13923396219(微信同号)
电子邮箱:23404761@qq.com
站长微信二维码
微信二维码