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

基本思路:

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

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

ServiceCollection的简单使用

在C#中,ServiceCollection是一个集合,用于注册和管理应用程序的依赖项。它是ASP.NET Core应用程序中的核心服务容器,负责管理服务的生命周期和依赖注入。下面详细介绍一下ServiceCollection的用法:

注册服务:

使用ServiceCollection可以通过AddTransient、AddScoped和AddSingleton方法向容器中注册服务。这些方法分别表示短暂、作用域和单例服务的生命周期。例如:

services.AddTransient<IMyService, MyService>();
services.AddScoped<IMyService, MyService>();
services.AddSingleton<IMyService, MyService>();

注册配置:

可以使用Configure方法向容器中注册配置。例如:

services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

这将在容器中注册一个名为"MyOptions"的配置实例,并将其绑定到appsettings.json文件中的对应节点。

注册中间件:

中间件是ASP.NET Core应用程序中的重要概念,它允许将请求和响应传递给下一个中间件,以便进行处理。可以使用UseMiddleware方法向容器中注册中间件。例如:

app.UseMiddleware<MyMiddleware>();

这将注册一个名为"MyMiddleware"的中间件,它将在请求管道中处理请求。

注册过滤器:

过滤器是ASP.NET Core应用程序中用于处理请求和响应的组件。可以使用AddMvc或AddControllers方法向容器中注册过滤器。例如:

services.AddMvc(options => {
options.Filters.Add<MyFilter>();
});

这将向MVC中注册一个名为"MyFilter"的过滤器,它将在请求管道中处理请求。

注册日志:

可以使用AddLogging方法向容器中注册日志服务。例如:

services.AddLogging();

这将注册日志服务,并向容器中添加ILoggerFactory和ILogger实现。

自定义注册:

除了上述方法外,还可以通过使用Add方法手动注册服务。例如:

services.Add(new ServiceDescriptor(typeof(IMyService), new MyService()));

这将手动注册一个名为"MyService"的服务,并将其实现类型指定为"MyService"。

总之,ServiceCollection是ASP.NET Core应用程序中非常重要的一部分,它允许开发人员管理应用程序中的依赖项和服务,并为应用程序提供扩展性和可维护性。

在ASP.NET Core应用程序中,services.AddOptions()方法用于将选项配置注册到应用程序的服务容器中。选项配置是一种将应用程序配置和代码分离的方法,它允许开发人员在运行时更改应用程序的行为而不需要更改代码。选项配置可以从不同的源(如appsettings.json文件、环境变量或命令行参数)中读取,以便在不同的环境中配置应用程序。

services.AddOptions()方法是ASP.NET Core提供的一种便捷方法,用于将选项配置添加到服务容器中。它会将IOptions、IOptionsSnapshot和IOptionsMonitor服务注册到容器中,以便应用程序的其他组件可以使用这些服务来访问选项配置。例如:

services.AddOptions<MyOptions>()
.Bind(Configuration.GetSection("MyOptions"))
.ValidateDataAnnotations();

在上面的示例中,services.AddOptions()方法将注册一个名为MyOptions的选项配置服务,并将其绑定到appsettings.json文件中的MyOptions节点。.ValidateDataAnnotations()方法用于启用数据注释验证,以确保选项配置符合指定的数据注释规则。通过这种方式,应用程序的其他组件可以使用IOptions服务来访问选项配置,并执行必要的验证和转换。

services.Configure(e=>config.GetSection("DB").Bind(e)),解释下这个代码,以及Bind的意思:

这段代码的作用是将应用程序配置文件(通常是appsettings.json)中的名为"DB"的配置节绑定到一个名为"DbSetting"的对象上,并将其注册为依赖项注入的服务。

具体地说,services.Configure(e => config.GetSection("DB").Bind(e))方法接受一个泛型类型参数DbSetting,表示要配置的选项类型。它还接受一个Lambda表达式,用于在运行时绑定选项值。

Lambda表达式中的e => config.GetSection("DB").Bind(e)代码表示将DbSetting对象的属性绑定到名为"DB"的配置节中的相应键值对。具体来说,config.GetSection("DB")方法表示从应用程序配置文件中获取名为"DB"的节,然后调用Bind(e)方法将其绑定到DbSetting对象的属性上。这样,当应用程序需要使用DbSetting对象时,它会从服务容器中获取该对象,并根据应用程序配置文件中的值进行配置。

Bind方法的作用是将配置节中的键值对绑定到指定对象的属性上。它接受一个参数,表示要绑定的对象。在上面的示例中,Bind(e)表示将配置值绑定到Lambda表达式中的参数e表示的DbSetting对象的属性上。

serviceProvider.CreateScope什么意思?

在ASP.NET Core应用程序中,serviceProvider.CreateScope()方法用于创建一个新的作用域,该作用域使用应用程序的服务容器作为其父级。作用域是一种轻量级的上下文,在作用域内创建的对象通常只在该作用域中有效,并且可以在作用域结束时自动释放。

具体来说,serviceProvider.CreateScope()方法返回一个IServiceScope对象,该对象表示新创建的作用域。可以使用该对象的ServiceProvider属性访问该作用域的服务容器,并在该作用域内使用该容器创建新的服务实例。

例如,以下代码创建了一个作用域,并在该作用域中获取MyService服务的实例:

using(var scope = serviceProvider.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var myService = scopedServices.GetRequiredService<MyService>();
//使用myService进行操作...
}

在上面的代码中,CreateScope()方法创建了一个新的作用域,并在using块中使用该作用域。然后,scopedServices.GetRequiredService()方法从该作用域的服务容器中获取MyService服务的实例。在using块结束时,作用域将被释放,任何在该作用域内创建的服务实例也将被释放。

C# DI中,如果类A依赖类B,在注册服务的时候,先写的IserviceCollection.AddXXX(A),后写的IserviceCollection.AddXXX(B),有影响没?

在C#的依赖注入(Dependency Injection)中,通常情况下,注册服务的顺序并不会对依赖关系造成直接的影响。

当使用依赖注入容器(如.NET Core中的IServiceCollection)注册服务时,服务的注册顺序通常只会影响到解析依赖关系的顺序。也就是说,如果类A依赖于类B,而你先注册A,后注册B,那么在解析A时,容器会自动解析B并将其注入到A的构造函数或属性中。

以下是一个示例说明注册服务顺序的影响:

services.AddSingleton<A>();
services.AddSingleton<B>();

上述代码中,先注册了A,后注册了B。当容器需要解析A时,会检查A的构造函数或属性的依赖关系,并自动解析B,并将其注入到A中。

然而,如果类A和类B之间存在循环依赖,即A依赖B,同时B也依赖A,那么注册服务的顺序将会变得重要。在这种情况下,你需要使用更高级的依赖注入模式,如构造函数注入、属性注入或方法注入,以避免循环依赖导致的问题。

综上所述,一般情况下,注册服务的顺序并不会对依赖注入造成直接影响。但在处理循环依赖时,注册服务的顺序可能需要特别注意。

C#之依赖注入DI(DependencyInjection)的更多相关文章

  1. ASP.NET Core依赖注入(DI)

    ASP.NET Core允许我们指定注册服务的生存期.服务实例将根据指定的生存时间自动处理.因此,我们无需担心清理此依赖关系,他将由ASP.NET Core框架处理.有如下三种类型的生命周期. 关于依 ...

  2. [Android]使用Dagger 2依赖注入 - DI介绍(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092083.html 使用Dagger 2依赖注入 - DI介 ...

  3. 浅析“依赖注入(DI)/控制反转(IOC)”的实现思路

    开始学习Spring的时候,对依赖注入(DI)——也叫控制反转(IOC)—— 的理解不是很深刻.随着学习的深入,也逐渐有了自己的认识,在此记录,也希望能帮助其他入门同学更深入地理解Spring.本文不 ...

  4. 依赖注入(DI)和Ninject,Ninject

    我们所需要的是,在一个类内部,不通过创建对象的实例而能够获得某个实现了公开接口的对象的引用.这种“需要”,就称为DI(依赖注入,Dependency Injection),和所谓的IoC(控制反转,I ...

  5. 控制反转IOC与依赖注入DI

    理解 IOC  http://www.cnblogs.com/zhangchenliang/archive/2013/01/08/2850970.html IOC 相关实例      的http:// ...

  6. Atitit js中的依赖注入di ioc的实现

    Atitit js中的依赖注入di ioc的实现 全类名(FQCN)为标识符1 混合请求模式1 使用类内  builder  即可..2 Service locator method走ok拦2 Jav ...

  7. 控制反转(Ioc)和依赖注入(DI)

    控制反转IOC, 全称 “Inversion of Control”.依赖注入DI, 全称 “Dependency Injection”. 面向的问题:软件开发中,为了降低模块间.类间的耦合度,提倡基 ...

  8. 控制反转IOC与依赖注入DI【转】

    转自:http://my.oschina.net/1pei/blog/492601 一直对控制反转.依赖注入不太明白,看到这篇文章感觉有点懂了,介绍的很详细. 1. IoC理论的背景我们都知道,在采用 ...

  9. 依赖注入(DI)和控制反转(IOC)

    依赖注入(DI)和控制反转(IOC) 0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只 ...

  10. MVC进阶之路:依赖注入(Di)和Ninject

    MVC进阶之路:依赖注入(Di)和Ninject 0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类, ...

随机推荐

  1. 第二届獬豸杯wp

    第二届獬豸杯wp 容器密码:}2N|n_yxdt!G/Ru}|_zdn$@?6@CD8E 计算机和手机部分已经在第二届 獬豸杯-复现 - 萧瑟迪亲传大弟子 - 博客园这里发过了,服务器部分自己又写了一 ...

  2. windows本地认证

    windows本地认证 本地认证概述 本地认证最简单的例子就是我们的电脑上存储着自己的账号密码,无论电脑是否联网,只要能开机,就可以输入账号密码登录到电脑中,工作组就是采用本地认证. 那认证流程是什么 ...

  3. Linux 系统出现异常排查思路

    16 系统出现异常排查思路16.1 查看用户信息16.1.1查看当前的用户# who 04:39:39 up  1:30,  1 user,  load average: 0.01, 0.01, 0. ...

  4. 寻找可靠的长久的存储介质之旅,以及背后制作的三个网页“图片粘贴转base64”、“生成L纠错级别的QR码”、“上传文件转 base64以及粘贴 base64 转可下载文件”

    其实对于目前的形式来说,虽然像 U 盘.固态硬盘.甚至光盘这些信息储存介质(设备)的容量越来越高,但是不得不说这些设备的可靠性依然像悬着的一块石头,虽然这块石头确实牢牢的粘在天花板上,但是毕竟是粘上去 ...

  5. 探秘Transformer系列之(19)----FlashAttention V2 及升级版本

    探秘Transformer系列之(19)----FlashAttention V2 及升级版本 目录 探秘Transformer系列之(19)----FlashAttention V2 及升级版本 0 ...

  6. 浅聊java运行机制

    Java程序运行机制 首先要清楚运行机制一般有两种 解释型 编译型 解释型: 顾名思义,就像有个人在旁边给你解释东西一样.比如看一本英文书,英语老师在旁边一句一句给你翻译解释.在写源代码时,每写一个 ...

  7. 【Java】关键字的使用

    java中有很多的关键字,他们的使用让Java语言变得更加灵活.易用,下面将介绍Java中最常用的几个关键字并说明其用法. 一.关键字:return--跳出 使用在方法体中,用于:① 结束方法② 针对 ...

  8. 【视频编辑】Pr视频编辑软件导出的视频声音有一段会变大怎么解决

    导出视频后为什么有段声音会突然变大? 也就是可能存在编辑器导出的时候有自动增益声音的行为. 具体描述: 工程文件里我没动过声音,工程文件里听也是很正常的,但是导出后有一小段音乐会突然变大(存在自动增益 ...

  9. study Python3【4】字符串的判断

    判断类型: result为True和False str = '1122abc' str.isalnum()是数字或者字母 str = 'MDCA' str.isalpha() 是字母 str = '1 ...

  10. nginx 安装及负载配置

    1.官网下载nginx源码包 http://nginx.org/download/nginx-1.8.1.tar.gz 2.上传到opt目录,解压 cd /opt tar -zxvf nginx-1. ...