最新文章 (全部类别)
LinERP - 线联ERP - 表格列头通用弹出菜单
LinERP - 线联ERP - 物料管理
CMS修改主菜单类别编码CategoryNo
LinERP - 线联ERP试用版下载
LinERP - 线联ERP客户服务
LinERP - 线联ERP主界面
LinERP - 线联ERP系统登陆
LinERP - 线联ERP简介
明细表支持批量操作删除
C# 图片按钮特效:鼠标移入变浅,移出恢复原样 (ImageButtonHover类)
C# 复制对象属性 CopyProperties扩展方法
线联ERP - 海康威视人脸考勤机设置固定IP、重启设备操作手册
线联ERP - 用户操作手册 - 系统初始化
线联ERP - 什么是主账套?
线联ERP - 用户操作手册 - 公司资料设置
C/S快速开发框架旗舰版CSFrameworkV6.0 - VS开发环境配置
修复BUG: CSFramework.EF框架 Remove<T>, RemoveWhere<T>
使用Xlight FTP文件服务器
印章公章在线免费制作
CSFrameworkV6.1旗舰版 - appsettings.json 配置文件增加参数
推荐:使用Photoshop制作ico图标
C#.NET格式化显示:数字末尾不显示0
SQL脚本:更新主表的完成标记FlagFinish=Y
FastReport.NET 设计器汉化&运行时汉化
VS2026/VS2022 关闭 “自动添加 using 命名空间”
C#将List<T>导出为 CSV 文件(Excel 直接打开,无需第三方组件包)
.NET8+EF.Core开发的大型ERP系统客户端4GB电脑测试报告
线联ERP - LinERP HR+考勤系统正式上线
CSFrameworkV6旗舰版 - 复制单据功能
QMS软件简介 | 成本核算报价系统软件简介
QMS - 五金制品行业成本核算报价系统 - 货币资料
QMS - 五金制品行业成本核算报价系统 - 公共字典管理
QMS - 五金制品行业成本核算报价系统 - 物料类别
QMS - 五金制品行业成本核算报价系统 - 物料管理
QMS - 五金制品行业成本核算报价系统 - 图纸文件管理
QMS - 五金制品行业成本核算报价系统 - 供应商管理
QMS - 五金制品行业成本核算报价系统 - 车型费管理
QMS - 五金制品行业成本核算报价系统 - 制程段配置
QMS - 五金制品行业成本核算报价系统 - 产品咨询
QMS五金制品行业报价系统 - 用户操作手册 - 成本中心核算
QMS五金制品行业报价系统 - 用户操作手册 - 报价单 - Quotation
QMS五金制品行业报价系统 - 用户操作手册 - 成本汇总表
QMS五金制品行业报价系统 - 用户操作手册 - 采购评估
QMS五金制品行业报价系统 - 用户操作手册 - 成本基础资料表
QMS五金制品行业报价系统 - 用户操作手册 - 新品可行性评估
QMS - 成本核算报价管理系统软件截图
QMS五金制品行业报价系统 - 用户操作手册 - 业务员管理
QMS五金制品行业报价系统 - 用户操作手册 - 客户管理
QMS五金制品行业报价系统 - 用户操作手册 - 工艺工序维护
QMS五金制品行业报价系统 - 用户操作手册 - 设备登记
.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
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程序集某个类的静态方法打开窗体
C# DataSet与XML互

热门标签
软件著作权登记证书 .NET .NET Reactor .NET5 .NET6 .NET7 .NET8 .NET9 .NETFramework AI编程 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数据访问层 DaMeng Database datalock DbFramework DeepSeek Demo教学 Demo实例 Demo下载 DevExpress教程 Docker Desktop DOM ECS服务器 EFCore EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR HR考勤系统 IDatabase IIS JavaScript LinERP LINQ MES MiniFramework MIS MSSQL MySql NavBarControl NETCore Node.JS NPM OMS Oracle资料 ORM PaaS POS PostgreSql Promise API PSD QMS 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 安装环境 版本区别 报表 备份还原 踩坑日记 操作手册 成本核算系统 达梦数据库 代码生成器 电子线材ERP 迭代开发记录 功能介绍 官方软件下载 国际化 海康威视考勤 基础资料窗体 架构设计 角色权限 开发sce 开发工具 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 框架升级 毛衫行业ERP 秘钥 密钥 企业网络维护 权限设计 软件报价 软件测试报告 软件加壳 软件简介 软件开发框架 软件开发平台 软件开发文档 软件授权 软件授权注册系统 软件体系架构 软件下载 软件著作权登记证书 软著证书 三层架构 设计模式 生成代码 实用小技巧 视频下载 收钱音箱 数据锁 数据同步 塑木地板行业ERP 推荐软件 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 线联ERP 详细设计说明书 新功能 信创 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线软件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序
联系我们
联系电话:13923396219(微信同号)
电子邮箱:23404761@qq.com
站长微信二维码
微信二维码