.NET Core 依赖注入 Microsoft.Extensions.DependencyInjection

文章目录

前言

依赖注入是一个非常重要的编程思想,就和面向过程和面向对象一样,IOC和控制反转是一种解耦的编程思想。

什么是依赖注入

[C#]理解和入门依赖注入
为什么要用IOC:inversion of controll反转控制(把创建对象的权利交给框架)

C# 使用依赖注入

框架介绍

目前.NET 有两个最优的依赖注入框架

  • Microsoft.Extensions.DependencyInjection:微软官方依赖注入框架,听说在.net core 8.0得到了最强的性能提升
  • Autofac:听说也是最强的依赖注入框架,性能强,开销低,功能完善。

Dependency injection in ASP.NET Core
Autofac 官网
深入浅出依赖注入容器------Autofac

Microsoft.Extensions.DependencyInjection

目前打算用微软的IOC,毕竟是官方背书,性能有保证。

C# 依赖注入IServiceCollection的AddSingleton方法使用

Nuget安装

简单单例使用

声明个测试类

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NETCore8.Models
{
    public class Person
    {
        public int Id { get; set; }

        public string ?Name { get; set; }

        public int Age { get; set; }


    }
}

主函数代码

csharp 复制代码
using Microsoft.Extensions.DependencyInjection;
using NETCore8.Models;
using Newtonsoft.Json;
using System.ComponentModel.Design;

namespace NETCore8
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //构造依赖注入容器
            IServiceCollection services = new ServiceCollection();
            //注入Person单例,生命周期暂时不展开
            services.AddSingleton<Person>();
            var builder = services.BuildServiceProvider();

            //初始化单例
            var res = builder.GetService<Person>();
            res.Name = "小刘";
            res.Age = 15;
            Console.WriteLine(JsonConvert.SerializeObject(res));

            //从容器中拿到Person单例,确认是否已被赋值为小刘
            var res2 = builder.GetService<Person>();
            Console.WriteLine(JsonConvert.SerializeObject(res2));
            //修改单例,查看容器中的单例是否被修改
            res2.Name = "小红";
            res2.Age = 23;


            //再从容器中拿出单例
            var res3 = builder.GetService<Person>();
            Console.WriteLine(JsonConvert.SerializeObject(res3));
            

            Console.WriteLine("Hello, World!");
            Console.ReadKey();
        }
    }
}

打印结果

这个说明这个单例一旦被修改了,容器中的数据就会被修改。但是这样仅仅是和全局静态的效果一样。依赖注入没有这么简单

自动装配

自动装配的意思就是自动依赖注入。就是你不需要主动去声明构造函数,IOC容器会自动帮你去使用构造函数。

举例

这里为了简单说明,这里只使用单例自动装配举例。

csharp 复制代码
namespace IOC_Test.Models
{
    public class Person
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public int Age {  get; set; }   
    }
}
csharp 复制代码
namespace IOC_Test.Services
{
    public class PersonService
    {
        public Person Person { get; set; }
        /// <summary>
        /// 无参构造函数
        /// </summary>
        public PersonService() {
            Person = new Person();
        }


        /// <summary>
        /// 有参构造函数,IOC是选择尽可能多的参数构造
        /// </summary>
        /// <param name="person"></param>
        public PersonService(Person person)
        {

            this.Person = person;
        }
    }
}

自动装配测试用例

csharp 复制代码
using IOC_Test.Models;
using IOC_Test.Services;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;

namespace IOC_Test
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IServiceCollection services = new ServiceCollection();
            //注入依赖
            services.AddSingleton<Person>();
            services.AddSingleton<PersonService>();
            //生成IOC容器
            var builder = services.BuildServiceProvider();

            //两次打印,第一次打印PersonService的Person
            {
                var res = builder.GetService<PersonService>();
                Console.WriteLine(JsonConvert.SerializeObject(res?.Person));
            }
            //修改Person,看看PersonService里面是不是会受影响
            {
                var res = builder.GetService<Person>();
                res.Name = "小王";
                res.Age = 10;
            }
            //再次打印,如果被修改,那么就说明是自动装配。如果没被修改,就说明没有将Person自动注入到PersonService
            {
                var res = builder.GetService<PersonService>();
                Console.WriteLine(JsonConvert.SerializeObject(res?.Person));
            }


            Console.WriteLine("Hello, World!");

            Console.ReadLine();
        }
    }
}

打印结果

自动装配执行顺序

测试用例

这里我们新建一个Dog

csharp 复制代码
namespace IOC_Test.Models
{
    public class Dog
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public int Age { get; set; }
    }
}

Person

csharp 复制代码
namespace IOC_Test.Models
{
    public class Person
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public int Age {  get; set; }   
    }
}

PersonService

csharp 复制代码
namespace IOC_Test.Services
{
    public class PersonService
    {
        public Person Person { get; set; }

        public Dog Dog { get; set; }

        /// <summary>
        /// 无参构造函数
        /// </summary>
        public PersonService() {
            Person = new Person();
        }
    }
}

主函数

csharp 复制代码
namespace IOC_Test
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IServiceCollection services = new ServiceCollection();
            //注入依赖
            services.AddSingleton<Person>();
            services.AddSingleton<PersonService>();
            services.AddSingleton<Dog>();
            //生成IOC容器
            var builder = services.BuildServiceProvider();

            //两次打印,第一次打印PersonService
            {
                var res = builder.GetService<PersonService>();
                Console.WriteLine(JsonConvert.SerializeObject(res));
            }
            //修改Person和Dog,看看PersonService里面是不是会受影响
            {
                var person = builder.GetService<Person>();
                person.Name = "小王";
                person.Age = 10;
                var dog = builder.GetService<Dog>();
                dog.Name = "旺财";
                dog.Age = 2;
            }
            //再次打印,查看自动装配如何执行
            {
                var res = builder.GetService<PersonService>();
                Console.WriteLine(JsonConvert.SerializeObject(res));
            }


            Console.WriteLine("Hello, World!");

            Console.ReadLine();
        }
    }
}
有歧义构造函数
csharp 复制代码
namespace IOC_Test.Services
{
    public class PersonService
    {
        public Person Person { get; set; }

        public Dog Dog { get; set; }

        /// <summary>
        /// 无参构造函数
        /// </summary>
        public PersonService() {
            Person = new Person();
        }

        public PersonService(Person person)
        {
            this.Person = person;
        }

        public PersonService(Dog dog) {
            this.Dog = dog;
        }
    }
}

如果构造函数出现歧义,比如这里既可以选择Person构造,又可以选择Dog构造,会报错

渐进式构造函数
csharp 复制代码
namespace IOC_Test.Services
{
    public class PersonService
    {
        public Person Person { get; set; }

        public Dog Dog { get; set; }

        /// <summary>
        /// 无参构造函数
        /// </summary>
        public PersonService() {
            Person = new Person();
        }

        public PersonService(Person person)
        {
            this.Person = person;
        }

        public PersonService(Person person,Dog dog) {
            this.Person= person;
            this.Dog = dog;
        }
    }
}

运行成功

循环依赖

Person注入Dog,Dog注入Person,看看效果如何

csharp 复制代码
namespace IOC_Test.Models
{
    public class Person
    {
        public Dog Dog { get; set; }

        public int Id { get; set; }

        public string Name { get; set; }

        public int Age { get; set; }
        public Person(Dog dog)
        {
            Dog = dog;
        }
    }
}
csharp 复制代码
namespace IOC_Test.Models
{
    public class Dog
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public int Age { get; set; }

        public Person Person { get; set; }
        public Dog(Person person)
        {
            Person = person;
        }
    }
}

自动装配结论

自动装配是尽可能主动去装配服务,如果出现装配歧义,循环依赖,那么就会主动抛出异常。自动装配可以极大的减少对构造函数维护,我们不需要知道服务是怎么声明的,IOC容器会帮助我们自动声明相互之间的依赖。这张图就能很好的解释自动装配的效果

手动装配

自动装配是由IOC容器自动装配的类。如果需要装配多个同类的服务,那就要手动进行区别了。

手动注入

csharp 复制代码
internal class Program
{
    static void Main(string[] args)
    {
        IServiceCollection services = new ServiceCollection();
        services.AddSingleton<Person>(sp =>
        {
            var res = new Person() {
                Name = "小红",
                Age = 19
            };
            return res;
        });
        //生成容器
        var builder = services.BuildServiceProvider();
        {
            var res = builder.GetService<Person>();
            Console.WriteLine(JsonConvert.SerializeObject(res));
        }
        

        Console.WriteLine("Hello, World!");

        Console.ReadLine();
    }
}

别名注入

csharp 复制代码
namespace IOC_Test
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IServiceCollection services = new ServiceCollection();
            services.AddKeyedSingleton<Person>("A",(sp,key) =>
            {
                var res = new Person() {
                    Name = "小红",
                    Age = 19
                };
                return res;
            });

            services.AddKeyedSingleton<Person>("B", (sp, key) =>
            {
                var res = new Person()
                {
                    Name = "小蓝",
                    Age = 23
                };
                return res;
            });
            //生成容器
            var builder = services.BuildServiceProvider();
            
            //获取服务,当Key找不到时自动返回Null
            {
                var res = builder.GetService<Person>();
                Console.WriteLine("获取默认服务");
                Console.WriteLine(JsonConvert.SerializeObject(res));
            }
            {
                var res = builder.GetKeyedService<Person>("A");
                Console.WriteLine("获取A,Person");
                Console.WriteLine(JsonConvert.SerializeObject(res));
            }
            {
                var res = builder.GetKeyedService<Person>("B");
                Console.WriteLine("获取B,Person");
                Console.WriteLine(JsonConvert.SerializeObject(res));
            }


            Console.WriteLine("Hello, World!");

            Console.ReadLine();
        }
    }
}

声明别名的服务将不会自动装配,即使声明的别名相同。建议使用多个不同名的服务来自动装配。手动声明别名需要手动装配对应关系

也可以在输入的时候主动拿到按照Key去寻找服务。

csharp 复制代码
internal class Program
{
    static void Main(string[] args)
    {
        IServiceCollection services = new ServiceCollection();
		//依赖注入是使用的时候去构造,所以声明顺序不影响实际运行顺序,有点类似于回调函数
        services.AddKeyedSingleton<Person>("A",(sp,key) =>
        {
            //Console.WriteLine(key);
            var res = new Person() {
                Name = "小红",
                Age = 19
            };
            return res;
        });
        services.AddKeyedSingleton<PersonService>("A", (sp, key) =>
        {
            return new PersonService(sp.GetKeyedService<Person>(key));
        });

        //生成容器
        var builder = services.BuildServiceProvider();
   
        //获取服务
        {
            var res = builder.GetKeyedService<Person>("A");
            Console.WriteLine("获取默认服务");
            Console.WriteLine(JsonConvert.SerializeObject(res));
        }
        //获取服务
        {
            var res = builder.GetKeyedService<PersonService>("A");
            Console.WriteLine("获取默认服务");
            Console.WriteLine(JsonConvert.SerializeObject(res));
        }

        Console.WriteLine("Hello, World!");

        Console.ReadLine();
    }
}

依赖注入的构造顺序

依赖注入是使用的时候去生成,而不是注入的时候生成

csharp 复制代码
namespace IOC_Test
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IServiceCollection services = new ServiceCollection();

            services.AddKeyedSingleton<Person>("A",(sp,key) =>
            {
                Console.WriteLine($"构造函数执行,key[{key}]");
                var res = new Person() {
                    Name = "小红",
                    Age = 19
                };
                return res;
            });
            //生成容器
            var builder = services.BuildServiceProvider();
       
            //获取服务
            {
                Console.WriteLine("获取Key[A]服务");
                var res = builder.GetKeyedService<Person>("A");
                Console.WriteLine(JsonConvert.SerializeObject(res));
            }

            Console.WriteLine("Hello, World!");

            Console.ReadLine();
        }
    }
}

结尾

IOC容器还有许多别的功能,比如别名,接口注入,注解注入,声明周期等。这个我还不太了解。现在的单例自动装配已经基本满足了我的功能,我以后有时间会去深入了解。

相关推荐
Ainnle4 小时前
微软 CEO 萨提亚・纳德拉:回顾过去十年,展望 AI 时代的战略布局
人工智能·microsoft
可喜~可乐14 小时前
C# WPF开发
microsoft·c#·wpf
微凉的衣柜14 小时前
微软在AI时代的战略布局和挑战
人工智能·深度学习·microsoft
yngsqq1 天前
一键打断线(根据相交点打断)——CAD c# 二次开发
windows·microsoft·c#
开发者每周简报1 天前
微软的AI转型故事
人工智能·microsoft
唐 城1 天前
微软致力于将非 OpenAI 模型添加到 365 Copilot 产品中
microsoft·copilot
阿7_QuQ1 天前
微软远程桌面APP怎么用
microsoft
仰望大佬0071 天前
Avalonia实例实战五:Carousel自动轮播图
数据库·microsoft·c#
码农君莫笑2 天前
Blazor项目中使用EF读写 SQLite 数据库
linux·数据库·sqlite·c#·.netcore·人机交互·visual studio
_oP_i2 天前
.NET Core 项目配置到 Jenkins
运维·jenkins·.netcore