文章目錄
- 前言
- 什么是依賴注入
- C# 使用依賴注入
- 框架介紹
- Microsoft.Extensions.DependencyInjection
- Nuget安裝
- 簡單單例使用
- 打印結果
- 自動裝配
- 舉例
- 自動裝配測試用例
- 打印結果
- 自動裝配執行順序
- 測試用例
- 有歧義構造函數
- 漸進式構造函數
- 循環依賴
- 自動裝配結論
- 手動裝配
- 手動注入
- 別名注入
- 依賴注入的構造順序
- 結尾
前言
依賴注入是一個非常重要的編程思想,就和面向過程和面向對象一樣,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安裝
簡單單例使用
聲明個測試類
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; }}
}
主函數代碼
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容器會自動幫你去使用構造函數。
舉例
這里為了簡單說明,這里只使用單例自動裝配舉例。
namespace IOC_Test.Models
{public class Person{public int Id { get; set; }public string Name { get; set; }public int Age { get; set; } }
}
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;}}
}
自動裝配測試用例
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
namespace IOC_Test.Models
{public class Dog{public int Id { get; set; }public string Name { get; set; }public int Age { get; set; }}
}
Person
namespace IOC_Test.Models
{public class Person{public int Id { get; set; }public string Name { get; set; }public int Age { get; set; } }
}
PersonService
namespace IOC_Test.Services
{public class PersonService{public Person Person { get; set; }public Dog Dog { get; set; }/// <summary>/// 無參構造函數/// </summary>public PersonService() {Person = new Person();}}
}
主函數
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();}}
}
有歧義構造函數
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構造,會報錯
漸進式構造函數
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,看看效果如何
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;}}
}
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容器自動裝配的類。如果需要裝配多個同類的服務,那就要手動進行區別了。
手動注入
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();}
}
別名注入
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去尋找服務。
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();}
}
依賴注入的構造順序
依賴注入是使用的時候去生成,而不是注入的時候生成
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容器還有許多別的功能,比如別名,接口注入,注解注入,聲明周期等。這個我還不太了解。現在的單例自動裝配已經基本滿足了我的功能,我以后有時間會去深入了解。