C#之依赖注入DI(DependencyInjection)

2022/9/4 14:23:11

本文主要是介绍C#之依赖注入DI(DependencyInjection),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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

基本思路:

  • 创建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>

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



这篇关于C#之依赖注入DI(DependencyInjection)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程