首页 > 编程语言 >C#之依赖注入DI(DependencyInjection)

C#之依赖注入DI(DependencyInjection)

时间:2022-09-03 23:22:29浏览次数:57  
标签:string DI C# void sp using services DependencyInjection public

依赖注入实际上是一种设计模式,它可以有效降低模块之间的耦合度。

基本思路:

  • 创建ServiceCollection对象

  • 用ServiceCollection对象进行注册服务

  • 用ServiceCollection创建ServiceProvider对象,通过ServiceProvider的GetService方法获取服务

而服务分为transient,scoped,singleton三种,其中transient是每次获取都是新的对象,scoped 是只有在范围以内的才是同一个对象,而singleton永远取到的是同一个对象,下面分别进行演示。

transient服务

using System;
using Microsoft.Extensions.DependencyInjection;
namespace DITest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddTransient<TestService>();
            using(var sp= services.BuildServiceProvider())
            {
                TestService t = sp.GetService<TestService>();
                t.Name = "JohnYang";
                t.SayHi();
                TestService t1 = sp.GetService<TestService>();
                Console.WriteLine(Object.ReferenceEquals(t, t1));
            }
            
        }
    }
    public class TestService
    {
        public string Name { get; set; }
        public void SayHi()
        {
            Console.WriteLine(Name);
        }
    }
}

output:

JohnYang
False

这确实也验证了transient服务,每次获取都是新的对象。

singleton服务

using System;
using Microsoft.Extensions.DependencyInjection;
namespace DITest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            //services.AddTransient<TestService>();
            services.AddSingleton<TestService>();
            //services.AddScoped<TestService>();
            using (var sp= services.BuildServiceProvider())
            {
                TestService t = sp.GetService<TestService>();
                t.Name = "JohnYang";
                t.SayHi();
                TestService t1 = sp.GetService<TestService>();
                Console.WriteLine(Object.ReferenceEquals(t, t1));
            }
            
        }
    }
    public class TestService
    {
        public string Name { get; set; }
        public void SayHi()
        {
            Console.WriteLine(Name);
        }
    }
}

output:

JohnYang
True

Scoped

using System;
using Microsoft.Extensions.DependencyInjection;
namespace DITest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            //services.AddTransient<TestService>();
            //services.AddSingleton<TestService>();
            services.AddScoped<TestService>();
            using (var sp= services.BuildServiceProvider())
            {
                TestService t, t1, t2;
                //指定范围
                using(IServiceScope scope = sp.CreateScope())
                {
                    //在scope中获取Scope相关的对象,需要用scope.ServiceProvider而不是sp!!
                    t = scope.ServiceProvider.GetService<TestService>();
                    t.Name = "JohnYang";
                    t.SayHi();
                    t1 = scope.ServiceProvider.GetService<TestService>();
                    Console.WriteLine(Object.ReferenceEquals(t, t1));
                }
                using (IServiceScope scope2 = sp.CreateScope())
                {
                    //在scope中获取Scope相关的对象,需要用scope.ServiceProvider而不是sp!!
                    t2 = scope2.ServiceProvider.GetService<TestService>();
                    Console.WriteLine(Object.ReferenceEquals(t2, t));
                }

            }
            
        }
    }
    public class TestService
    {
        public string Name { get; set; }
        public void SayHi()
        {
            Console.WriteLine(Name);
        }
    }
}

output:

JohnYang
True
False

结果也验证了,在同一个范围是同一个服务,但不同范围,获取的不是同一个服务的结论。

需要注意的事项:

  • 不要再长声明周期的对象中引用比它短的生命周期的对象,因为短的生命周期的对象被销毁的时候,长声明周期的对象对它的引用将受影响。

  • 声明周期的选择:如果类无状态(无属性和成员变量),建议为singleton;如果类有状态,且有Scope控制,建议为Scoped,因为通常这种Scope控制下的代码都是运行在同一个线程中的,没有并发修改的问题;在使用Transient的时候要谨慎。

服务定位器

接口的形式:

using System;
using Microsoft.Extensions.DependencyInjection;
namespace DITest
{
    internal class Program
    {
        static void Main()
        {
            ServiceCollection services = new ServiceCollection();
           services.AddScoped<ITestService,TestService>();//第一个是服务的接口,第二个是实现服务的对象
            using(var sp = services.BuildServiceProvider())
            {
                ITestService testService = sp.GetService<ITestService>();
                testService.Name = "JohnYang";
                testService.SayHi();
                Console.WriteLine(testService.GetType());
            }
        }
        
    }
   public interface ITestService
    {
        public string Name { get; set; }
        public void SayHi();
    }
    public class TestService:ITestService
    {
        public string Name { get; set; }
        public void SayHi()
        {
            Console.WriteLine(Name);
        }
    }
}

output:

JohnYang
DITest.TestService

GetService<T>中的T必须与AddXXX<T,T1>中的T是一致的,否则,取不到,返回null,以上面例子来讲,如果GetService<TestService>就报错,因为注册的是ITestServie,而不是TestSerive

T GetRequiredService<T>()如果获取不到对象,则抛异常。

IEnumerable<T> GetServices<T>()适用于可能有很多满足条件的服务。

using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
namespace DITest
{
    internal class Program
    {
        static void Main()
        {
            ServiceCollection services = new ServiceCollection();
           services.AddScoped<ITestService,TestService>();//第一个是服务的接口,第二个是实现服务的对象
            using(var sp = services.BuildServiceProvider())
            {
                IEnumerable<ITestService> testServices = sp.GetServices<ITestService>();
                foreach(var t in testServices)
                {
                    Console.WriteLine(t.GetType());
                }
            }
        }
        
    }
   public interface ITestService
    {
        public string Name { get; set; }
        public void SayHi();
    }
    public class TestService:ITestService
    {
        public string Name { get; set; }
        public void SayHi()
        {
            Console.WriteLine(Name);
        }
    }
}

output:

DITest.TestService

当注册了多个服务的时候,GetServices返回的是所有的实现的对象,而GetServie返回的是最后一个注册的服务。

IEnumerable<object> GetServices(Type serviceType)

依赖注入的“传染性”

依赖注入是有“传染性”的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值,但是如果一个对象是程序员手动创建的,那么这个对象就和DI没有关系,它的构造函数中声明的类型参数就不会被自动赋值。.NET的DI默认是构造函数注入。

这也是依赖注入非常强大的地方,通过DI创建的对象,该对象构造函数中的参数也会自动的被创建。

Demo如下:

using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
namespace DITest
{
    internal class Program
    {
        static void Main()
        {
           ServiceCollection services = new ServiceCollection();
            //注册各种服务
            services.AddScoped<Controller>();
            services.AddScoped<ILog, LogImpl>();
            services.AddScoped<IStorage, StorageImpl>();
            services.AddScoped<IConfig, ConfigImpl>();

            using(var sp = services.BuildServiceProvider())
            {
                Controller controller = sp.GetRequiredService<Controller>();
                controller.Test();
            }
            Console.ReadKey();
        }
        
    }
    class Controller
    {
        private readonly ILog log;
        private readonly IStorage storage;
        public Controller(ILog log, IStorage storage)//构造函数注入
        {
            this.log = log;
            this.storage = storage;
        }

        public void Test()
        {
            log.Log("开始上传");
            storage.Save("asdkks", "1.txt");
            log.Log("上传完毕");
        }
    }

    /// <summary>
    /// 日志服务
    /// </summary>
    interface ILog
    {
        public void Log(string msg);
    }
    /// <summary>
    /// 日志实现类
    /// </summary>
    class LogImpl : ILog
    {
        public void Log(string msg)
        {
            Console.WriteLine("日志:"+msg);
        }
    }
    /// <summary>
    /// 配置服务
    /// </summary>
    interface IConfig
    {
        public string GetValue(string name);
    }
    /// <summary>
    /// 配置实现类
    /// </summary>
    class ConfigImpl : IConfig
    {
        public string GetValue(string name)
        {
            return "hello";
        }
    }

    interface IStorage
    {
        public void Save(string content, string name);
    }

    class StorageImpl : IStorage
    {
        private readonly IConfig _config;
        public StorageImpl(IConfig config)//构造函数注入,当DI创建StorageImpl时候,框架自动创建IConfig服务
        {
            _config = config;
        }

        public void Save(string content, string name)
        {
            string server=_config.GetValue("server");
            Console.WriteLine($"向服务器{server}的文件名{name}上传{content}");
        }
    }
}

output:

日志:开始上传
向服务器hello的文件名1.txt上传asdkks
日志:上传完毕

如果后续,更改配置,则业务代码不用动,只需要

class DbConfigImpl : IConfig
    {
        public string GetValue(string name)
        {
            return "hello db";
        }
    }

然后,把之前IConfig的服务更改为DbConfigImpl,就可以了。


services.AddScoped<IConfig, DbConfigImpl>

因此,降低了模块之间的耦合度。

标签:string,DI,C#,void,sp,using,services,DependencyInjection,public
From: https://www.cnblogs.com/johnyang/p/16653953.html

相关文章

  • 查询字节串编码类型的模块chardet
    这个模块需要安装wgethttps://files.pythonhosted.org/packages/fc/bb/a5768c230f9ddb03acc9ef3f0d4a3cf93462473795d18e9535498c8f929d/chardet-3.0.4.tar.gz解......
  • Could not start SASL: b'Error in sasl_client_start (-4) SASL(-4)
    使用pyhive的时候出现了这个问题,我使用的是anaconda3。查了很多帖子都不能解决。参考:https://blog.csdn.net/weixin_43142260/article/details/115198097https://blog.c......
  • 笔记:sentinel配置链路时web-context-unify=false不生效
    @ConfigurationpublicclassdemoConfig{/***@NOTE在spring-cloud-alibabav2.1.1.RELEASE及前,sentinel1.7.0及后,关闭URLPATH聚合需要通过该方式,spring-clou......
  • PHP---导入Excel数据
    最近在开发一个系统,需要做大量的数据处理,特别是导入数据,使用的框架thinkphp6,就目前来说,下面介绍一种逐行添加的方法:注意:以下这种方式,仅提供开发思路,读取一行,添加一行。但......
  • 力扣leetcode刷题记录1----
    【以下题目来源均来自力扣leetcode】 595.大的国家World 表:【描述】name是这张表的主键。这张表的每一行提供:国家名称、所属大陆、面积、人口和GDP值。【问题】......
  • NC16544 简单环
    题目链接题目题目描述给定一张n个点m条边的无向图,求出图中所有简单环的数量。(简单环:简单环又称简单回路,图的顶点序列中,除了第一个顶点和最后一个顶点相同外,其余顶点不......
  • UAC实现原理
    UAC实现原理:当用户登录系统成功后,系统会为用户生成一个accessToken。该用户调用的每一个进程都会有一个AccessTokencopy。当进程要访问某个securableobject时,系统会......
  • SpringMVC学习笔记(四)——REST风格
    1.什么是REST RESTful(REST风格)是一种当前比较流行的互联网软件架构模式,它充分并正确地利用HTTP协议的特性,为我们规定了一套统一的资源获取方式,以实现不同终端之间(客......
  • NC16122 郊区春游
    题目链接题目题目描述今天春天铁子的班上组织了一场春游,在铁子的城市里有n个郊区和m条无向道路,第i条道路连接郊区Ai和Bi,路费是Ci。经过铁子和顺溜的提议,他们决定去其中......
  • 最短路算法之 Dijkstra
    部分内容参考了李煜东的《算法竞赛进阶指南》,在此声明。单源最短路径单源最短路径问题,是说,给定一张有向图(无向图)\(G=(V,E)\),\(V\)是点集,\(E\)是边集,\(|V|=n\),\(|......