首页 > 其他分享 >.NET Core 核心知识点(四) -- 初会依赖注入

.NET Core 核心知识点(四) -- 初会依赖注入

时间:2024-07-24 17:30:25浏览次数:10  
标签:知识点 服务 Name Core -- services provider serviceImpl1 sayHi

控制反转、服务定位器、依赖注入


  控制反转:使用对象或者服务的时候,不需要自己去创建/new服务,而是在使用的时候直接声明,容器会自动分配一个服务实例。相当于自己用发电机发电使用和利用电网公司的电的区别,自己发电,我需要一台发电机,安装发电机,自己设置电压,频率等等,而使用电网公司的只需要花钱,就能使用;(自己创建和问别人要的区别)

  • 依赖注入(Dependency Injection,DI)是控制反转(Inversion Of Control,IOC)思想的一种实现方式
  • 依赖注入简化模块的组装过程,降低模块之间的耦合度
  • 实现控制反转的两种方式:1.服务定位器,2.依赖注入
//1.自己发电的方式
var connSettings = ConfigurationManager.ConnectionStrings["connStr"];
string connStr = connSettings.ConnectionString;
SqlConnection conn = new SqlConnection(connStr);

//2.服务定位器实现控制反转(伪代码)
IDbConnection conn = ServiceLocator.GetService<IDbConnection>();

//3.依赖注入实现控制反转(伪代码)
class Demo
{
    //只需要声明,表示我需要一个数据库连接的实例对象
    public IDbConnection Conn {get;set;}
    public void InsertDB()
    {
        IDbCommand command = Conn.CreateCommand();
    }
}
  • Dependency Injection的几个概念
  1. 服务(service):对象,用来使用的
  2. 注册服务:将服务注册到相应的容器中
  3. 服务容器:负责管理注册的服务
  4. 查询服务:创建对象及关联对象
  5. 对象生命周期:Transient(瞬态)、Scoped(范围)、Singletion(单例)
namespace DependencyInjectionDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            //新建服务容器
            ServiceCollection services = new ServiceCollection();
            //将服务注册到容器中
            services.AddTransient<TestServiceImpl1>();
            //再从服务容器中获取指定的服务来使用
            using (ServiceProvider provider= services.BuildServiceProvider())
            {
                TestServiceImpl1 serviceImpl1 = provider.GetService<TestServiceImpl1>();
                serviceImpl1.Name = "Tim Duncan";
                serviceImpl1.sayHi();
            }
        }
    }



    interface ITestService {
        public string Name { get; set; }

        public void sayHi();
        
    }

    class TestServiceImpl1:ITestService
    {
        public string Name { get; set; }
        public void sayHi()
        {
            Console.WriteLine("hello,my name is "+ Name);
        }
    }

    class TestServiceImpl2 : ITestService
    {
        public string Name { get; set; }
        public void sayHi()
        {
            Console.WriteLine("你好,我的名字是 " + Name);
        }
    }
}
  • 服务的生命周期
  • 1.Service.AddTransient<T>,创建一个瞬时的对象,每次创建都是一个新的对象服务;
        static void Main(string[] args)
        {
            //新建服务容器
            ServiceCollection services = new ServiceCollection();
            //将服务注册到容器中(瞬态注册)
            services.AddTransient<TestServiceImpl1>();
            //再从服务容器中获取指定的服务来使用
            using (ServiceProvider provider= services.BuildServiceProvider())
            {

                TestServiceImpl1 serviceImpl1 = provider.GetService<TestServiceImpl1>();
                serviceImpl1.Name = "james";
                serviceImpl1.sayHi();

                TestServiceImpl1 serviceImpl2 = provider.GetService<TestServiceImpl1>();
                serviceImpl2.Name = "kobe";
                serviceImpl2.sayHi();
                //比较两个对象是否相同
                Console.WriteLine(object.ReferenceEquals(serviceImpl1, serviceImpl2));
                //再次输出serviceImpl1
                serviceImpl1.sayHi();
            }
        }

         可以看到,两个服务的是不相同的,并且再次调用第一个服务的方法,服务里面的值也不会改变,这个就是瞬时获取,每个服务实例都不一样;

          2.Service.Singletion<T>,单例模式,每次拿到都都是同一个对象实例
        static void Main(string[] args)
        {
            //新建服务容器
            ServiceCollection services = new ServiceCollection();
            //将服务注册到容器中(单例模式注册)
            services.AddSingleton<TestServiceImpl1>();
            //再从服务容器中获取指定的服务来使用
            using (ServiceProvider provider= services.BuildServiceProvider())
            {

                TestServiceImpl1 serviceImpl1 = provider.GetService<TestServiceImpl1>();
                serviceImpl1.Name = "james";
                serviceImpl1.sayHi();

                TestServiceImpl1 serviceImpl2 = provider.GetService<TestServiceImpl1>();
                serviceImpl2.Name = "kobe";
                serviceImpl2.sayHi();
                //比较两个对象是否相同
                Console.WriteLine(object.ReferenceEquals(serviceImpl1, serviceImpl2));
                //再次输出serviceImpl1
                serviceImpl1.sayHi();
            }
        }

        可以看到,这个时候两个对象是一样的,并且第一个对象经过第二个对象赋值之后,再重新打印,属性值已经变为第二次赋值的kobe了,这就是单例模式,永远都是一个对象,每次赋值都会把之前的覆盖掉,这种适合于创建不需要状态的服务对象,比如一些辅助帮助类等。

  • 3.AddScoped<T>,是指对一定范围内的代码有作用,出了这个范围,服务自动被释放,调用对象的IDispose方法
        static void Main(string[] args)
        {
            //新建服务容器
            ServiceCollection services = new ServiceCollection();
            //将服务注册到容器中(范围模式注册)
            services.AddScoped<TestServiceImpl1>();
            //再从服务容器中获取指定的服务来使用
            using (ServiceProvider provider= services.BuildServiceProvider())
            {
                //作用域范围
                using (IServiceScope scope1 = provider.CreateScope())
                {
                    var serviceImpl1 =  scope1.ServiceProvider.GetService<TestServiceImpl1>();
                    serviceImpl1.Name = "james";
                    serviceImpl1.sayHi();

                    var serviceImpl2 = scope1.ServiceProvider.GetService<TestServiceImpl1>();
                    serviceImpl1.Name = "kobe";
                    serviceImpl1.sayHi();
                    //比较两个对象是否相同
                    Console.WriteLine(object.ReferenceEquals(serviceImpl1, serviceImpl2));
                    //再次输出serviceImpl1
                    serviceImpl1.sayHi();
                }
            }
        }

可以看到,在同一个using范围内,Scope模式获取的两个服务对象是一样的 ,类似于上面单例模式

下面看另外一种情况,在不同作用域中定义的对象进行比较

        static void Main(string[] args)
        {
            //新建服务容器
            ServiceCollection services = new ServiceCollection();
            //将服务注册到容器中(作用域范围模式注册)
            services.AddScoped<TestServiceImpl1>();
            //再从服务容器中获取指定的服务来使用
            using (ServiceProvider provider= services.BuildServiceProvider())
            { 
                TestServiceImpl1 outSideService;
                //作用域范围1
                using (IServiceScope scope1 = provider.CreateScope())
                {
                    var serviceImpl1 =  scope1.ServiceProvider.GetService<TestServiceImpl1>();
                    serviceImpl1.Name = "james";
                    serviceImpl1.sayHi();
                    outSideService = serviceImpl1;
                }
                //作用域范围2
                using (IServiceScope scope2 = provider.CreateScope())
                {
                    var serviceImpl1 = scope2.ServiceProvider.GetService<TestServiceImpl1>();
                    serviceImpl1.Name = "james";
                    serviceImpl1.sayHi();
                    //比较外部服务对象和内部服务对象
                    Console.WriteLine(object.ReferenceEquals(outSideService, serviceImpl1));
                }
            }
        }

 

  • 4.需要注意的点
  1. 不要再长生命周期的对象中引用比它短的生命周期的对象,这样会默认抛异常
  2. 生命周期的选择:如果类无状态,没有成员属性变量,就是简单的一些处理逻辑返回值等,定义为Singletion;如果有状态,且有Scope控制,建议为Scoped,因为通常这种Scope控制下代码都是运行在同一个线程中的,并没有并发修改的问题;在使用Transient的时候需要谨慎
其他注册方法

  • 我们一般把服务定义为接口,要用的对象为接口的实现类,如下:TestServiceImpl2为接口ITestService的实现类
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<ITestService, TestServiceImpl2>();

            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                ITestService implService = sp.GetService<ITestService>();
                implService.Name = "John";
                implService.sayHi();
                Console.WriteLine(implService.GetType());
            }
        }
    }
    interface ITestService {
        public string Name { get; set; }

        public void sayHi();
        
    }

    class TestServiceImpl1:ITestService
    {
        public string Name { get; set; }
        public void sayHi()
        {
            Console.WriteLine("hello,my name is "+ Name);
        }
    }

    class TestServiceImpl2 : ITestService
    {
        public string Name { get; set; }
        public void sayHi()
        {
            Console.WriteLine("你好,我的名字是 " + Name);
        }
    }
}

运行结果为:

  • 在provider.GetService<T>的方法里我们也可以使用typeof(T)的方式来获取服务对象:如下:
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<ITestService, TestServiceImpl2>();
            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                ITestService implService =(ITestService) sp.GetService(typeof(ITestService));
                implService.Name = "John";
                implService.sayHi();
                Console.WriteLine(implService.GetType());
            }
        }

运行结果也是一样的,如下:

  • 还有可以使用provider.GetRequiredService<T>()方法,这种方式,如果泛型T不是注册时的服务类型,则会抛出异常,所以,在获取服务对象时的类型必须与注册时候的类型一致:

 

  • provider.GetServices(),获取该注册服务的多个实现类集合。如果注册服务是一个接口,此接口下面有多个实现类的话,那么使用GetServices方法可以遍历出所有的实现类(前提是这个实现类必须跟随接口一起注册在服务容器中):
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<ITestService, TestServiceImpl1>();
            services.AddScoped<ITestService, TestServiceImpl2>();
            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                var implServices = sp.GetServices<ITestService>();
                foreach (var item in implServices)
                {
                    Console.WriteLine(item.GetType()); 
                }
            }
        }

运行结果如下:两个实现类都打印出来了

  • 注意,如果一个服务接口注册了多个实现类,此时使用GetService<T>方法获取的是最后一个注册到容器中的子类类型,如下:

 

打印结果如下:

标签:知识点,服务,Name,Core,--,services,provider,serviceImpl1,sayHi
From: https://blog.csdn.net/liyumin2006/article/details/140640287

相关文章

  • 查看Oracle执行计划以及真实执行计划
    在Oracle数据库中,查看SQL语句的执行计划可以通过以下几种方法:使用AUTOTRACEAUTOTRACE提供一个简单的方法来查看SQL语句的执行计划和统计信息。你可以在SQL*Plus中使用它:启用AUTOTRACE:SETAUTOTRACEON执行你的SQL语句,Oracle会自动显示执行计划和统计信息......
  • 二维数组的定义
    一、二维数组的定义类型名变量名[常量表达式][常量表达式] 这个方括号只是说明他的类型。二维数组类似一个表单如下图所示二、二维数组的引用数组名[下标][下标]第一个是行号,第二个是列号,超过所容纳的范围,会造成越界访问。三、二维数组的特点1、本质上有三个元素,......
  • k8s配置文件之deployment配置
    deployment使用并管理rs,算是更高一层的概念,这是现在比较常用的部署app的方式。deployment为pod和rs提供声明式更新(而非命令式)。支持滚动更新(rollingUpdate),支持回滚操作资源配置主要分五类来定义内容 一:apiVersion【string】 APIVersion定义对象表示的版本,此处为:apps/......
  • 一文说透ConcurrentHashMap及大厂面试题
    23年毕业半年被裁后,一个月斩获大厂offer,“跟着周哥走,offer手里有”。文中有周哥50+场面试总结出的必会面试题。本期说一下ConcurretHashmap及相关知识点的面试题及答案。注:接下来的内容来自本人整理的面试秘籍。点击此处,免费获取面试秘籍jdk1.7中和jdk1.8中ConcurretH......
  • 暑期第三周总结
    本周,我投入了大量的时间在学习和实践大数据技术,总计22小时的学习时间和12小时的编码实践,以及4小时的问题解决时间。通过这段时间的努力,我不仅掌握了Hadoop的配置和管理,还深入了解了大数据的生态和原理。一周的具体学习和生活总结如下:周一:技术学习日活动:投入学习Hive和Spark,这两......
  • Linux系统安装Cobol语言及IBM大型机模拟软件Hercules
     COBOL(CommonBusiness-OrientedLanguage)起源于50年代中期,是一种面向过程的高级程序设计语言,主要用于商业和数据处理领域。经过不断发展和标准化,已成为国际上应用最广泛的商业编程语言之一,在某red书上还有招聘COBOL程序员去日本的帖子,个人害怕噶腰子所以不推荐。COBOL语言具......
  • Java内存模型全解析:解决共享变量可见性与指令重排难题
    本期说一下Java内存模型(JavaMemoryModel,JMM)及共享变量可见性问题。“以下内容出自本人整理的面试秘籍。点击此处,无套路免费获取面试秘籍JMM是什么?答:Java内存模型(JavaMemoryModel,JMM)抽象了线程和主内存之间的关系就比如说线程之间的共享变量必须存储在主内存......
  • Substance designer常用节点介绍
    RGBASplitRGBAMerge——分离四通道,合并四通道Distance——SDF,扩大图片像素HistogramScan——类似于smoothstep,硬化或模糊边缘Bevel——Distance只能扩大,但是这个可以缩小和扩大InvertGrayscale—— 反转Transformation2D—— 开放手动调整缩放之类的参数EdgeDe......
  • 3种常见的数据分析场景梳理
    作者:咕猫(唐羽影)数据分析已经逐步的被应用到工作/生活的各个领域,数据分析的本质就是从繁杂的数据中看到其深层次的规律和机理,从而对未发生的事情进行预测!那么,了解一下常见的3种数据分析场景,即描述现状、分析原因、预测未来,来加深对数据分析的理解。数据分析已经逐步的被应用......
  • How do I increase max_new_tokens
    题意:怎样增加 max_new_tokens 的值问题背景:I'mfacingthiserrorwhilerunningmycode:    运行代码时遇到如下错误:ValueError:Inputlengthofinput_idsis1495,but max_length issetto20.Thiscanleadtounexpectedbehavior.Youshouldcons......