.NET 依赖注入深入详解
<p>原为链接:https://www.cnblogs.com/ysmc/p/18796964</p><h1>.NET 依赖注入深入详解</h1>
<p>依赖注入(Dependency Injection, DI)是.NET Core .NET 5/6/7/8/9/10+中最重要的设计模式之一,下面我将从多个维度详细解释它的工作原理和使用方法。</p>
<h2>一、核心概念解析</h2>
<h3>1. 什么是依赖?</h3>
<p>当一个类A需要类B才能正常工作时,我们就说类A"依赖"于类B。例如:</p>
<div class="md-code-block">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> OrderService
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger _logger; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> OrderService依赖于ILogger</span>
<span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> OrderService(ILogger logger)
{
_logger </span>=<span style="color: rgba(0, 0, 0, 1)"> logger;
}
}</span></pre>
</div>
</div>
<h3>2. 传统方式的问题</h3>
<p>没有DI时,我们可能会这样写:</p>
<div class="md-code-block">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> OrderService
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> FileLogger _logger = <span style="color: rgba(0, 0, 255, 1)">new</span> FileLogger(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 直接创建具体实现
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
}</pre>
</div>
</div>
<p>这种方式的缺点:</p>
<ul>
<li>
<p>紧耦合:OrderService直接依赖FileLogger</p>
</li>
<li>
<p>难以测试:无法轻松替换为测试用的Logger</p>
</li>
<li>
<p>难以修改:如果要改用DatabaseLogger,需要修改OrderService代码</p>
</li>
</ul>
<h2>二、.NET DI 容器详解</h2>
<h3>1. 服务注册方式</h3>
<p>在Program.cs/Startup.cs中有三种主要注册方式:</p>
<div class="md-code-block">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1. 注册具体类型</span>
services.AddTransient<EmailService><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2. 注册接口-实现映射</span>
services.AddScoped<IEmailService, SmtpEmailService><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3. 注册现有实例</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> logger = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FileLogger();
services.AddSingleton</span><ILogger>(logger);</pre>
</div>
</div>
<h3>2. 生命周期详解</h3>
<table align="left">
<thead>
<tr><th>生命周期</th><th>描述</th><th>适用场景</th><th>示例</th></tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">Transient</td>
<td>每次请求都创建新实例</td>
<td>轻量级、无状态服务</td>
<td>工具类、DTO映射</td>
</tr>
<tr>
<td>Scoped</td>
<td>同一请求内共享实例</td>
<td>需要请求上下文的服务</td>
<td>DbContext、用户会话</td>
</tr>
<tr>
<td>Singleton</td>
<td>整个应用生命周期一个实例</td>
<td>全局共享资源</td>
<td>配置、缓存、日志</td>
</tr>
</tbody>
</table>
<h3> </h3>
<h3> </h3>
<h3> </h3>
<h3>3. 高级注册技巧</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册多个实现</span>
services.AddTransient<IMessageService, SmsService><span style="color: rgba(0, 0, 0, 1)">();
services.AddTransient</span><IMessageService, EmailService><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 命名注册</span>
services.AddTransient<IMessageService>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SMS</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(SmsService));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 泛型注册</span>
services.AddScoped(<span style="color: rgba(0, 0, 255, 1)">typeof</span>(IRepository<>), <span style="color: rgba(0, 0, 255, 1)">typeof</span>(Repository<><span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 委托工厂</span>
services.AddTransient<IService>(sp =>
<span style="color: rgba(0, 0, 255, 1)">new</span> Service(sp.GetRequiredService<IOtherService>()));</pre>
</div>
<h2>三、注入方式大全</h2>
<h3>1. 构造函数注入(最推荐)</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ProductController
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> IProductService _service;
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> ProductController(IProductService service)
{
_service </span>= service; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 由DI容器自动注入</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}</span></pre>
</div>
<h3>2. 方法注入</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ReportGenerator
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Generate(IReportFormatter formatter)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用方法参数注入</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}</span></pre>
</div>
<h3>3. 属性注入(不推荐但某些场景需要,如blazor)</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> NotificationService
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 需要特定属性标记</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> ILogger Logger { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
</div>
<h3>4. 从容器直接解析(应避免,但有时必要)</h3>
<div class="md-code-block">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> service = serviceProvider.GetRequiredService<IMyService>();</pre>
</div>
</div>
<h2>四、实际应用场景</h2>
<h3>1. 分层架构中的DI</h3>
<div class="md-code-block">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 基础设施层</span>
services.AddDbContext<AppDbContext>(options =><span style="color: rgba(0, 0, 0, 1)">
options.UseSqlServer(Configuration.GetConnectionString(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Default</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 应用层</span>
services.AddScoped<IOrderService, OrderService><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 领域层</span>
services.AddTransient<IDomainEventDispatcher, DomainEventDispatcher><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 表现层</span>
services.AddControllersWithViews();</pre>
</div>
</div>
<h3>2. 选项模式(Options Pattern)</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册配置</span>
services.Configure<EmailSettings>(Configuration.GetSection(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">EmailSettings</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注入使用</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> EmailService
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> EmailSettings _settings;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> EmailService(IOptions<EmailSettings><span style="color: rgba(0, 0, 0, 1)"> options)
{
_settings </span>=<span style="color: rgba(0, 0, 0, 1)"> options.Value;
}
}</span></pre>
</div>
<h3>3. 日志集成</h3>
<div class="md-code-block">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner">
<div class="md-code-block-infostring">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ProductService
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger<ProductService><span style="color: rgba(0, 0, 0, 1)"> _logger;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> ProductService(ILogger<ProductService><span style="color: rgba(0, 0, 0, 1)"> logger)
{
_logger </span>= logger; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 自动注入日志系统</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}</span></pre>
</div>
</div>
</div>
</div>
</div>
<h2>五、高级主题</h2>
<h3>1. 第三方容器集成</h3>
<div class="md-code-block">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Autofac示例</span>
builder.Host.UseServiceProviderFactory(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AutofacServiceProviderFactory())
.ConfigureContainer</span><ContainerBuilder>(builder =><span style="color: rgba(0, 0, 0, 1)">
{
builder.RegisterModule(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyAutofacModule());
});</span></pre>
</div>
</div>
<h3>2. 装饰器模式</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 原始服务</span>
services.AddScoped<IDataService, DataService><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 装饰器</span>
services.Decorate<IDataService, CachingDataService>();</pre>
</div>
<h3>3. 验证服务注册</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 检查所有服务是否已注册</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> provider =<span style="color: rgba(0, 0, 0, 1)"> services.BuildServiceProvider();
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> service <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> services)
{
provider.GetRequiredService(service.ServiceType); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 会抛出异常如果未注册</span>
}</pre>
</div>
<h2>六、最佳实践</h2>
<ol start="1">
<li>
<p><strong>构造函数保持简单</strong>:只注入必要的依赖</p>
</li>
<li>
<p><strong>避免服务定位器模式</strong>:不要滥用GetService</p>
</li>
<li>
<p><strong>注意生命周期</strong>:避免Scoped服务被Singleton服务引用</p>
</li>
<li>
<p><strong>使用接口</strong>:尽量依赖抽象而非具体实现</p>
</li>
<li>
<p><strong>避免过度注入</strong>:如一个类注入超过5个服务,考虑重构</p>
</li>
</ol>
<h2>七、常见问题解决</h2>
<h3>循环依赖问题</h3>
<div class="md-code-block">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 错误示例</span>
<span style="color: rgba(0, 0, 255, 1)">class</span> A { <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> A(B b) {} }
</span><span style="color: rgba(0, 0, 255, 1)">class</span> B { <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> B(A a) {} }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解决方案:
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1. 重构设计,提取公共逻辑到第三个类
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2. 使用属性注入或方法注入替代构造函数注入</span></pre>
</div>
</div>
<h3>多实现选择</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册多个实现</span>
services.AddTransient<IMessageService, EmailService><span style="color: rgba(0, 0, 0, 1)">();
services.AddKeyedTransient</span><IMessageService, SmsService>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SMS</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解析所有实现</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> services = provider.GetServices<IMessageService><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 命名解析</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> smsService = provider.GetRequiredKeyedService<IMessageService>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SMS</span><span style="color: rgba(128, 0, 0, 1)">"</span>);</pre>
</div>
<p>感谢各位大佬的观看!</p>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自博客园,作者:一事冇诚,转载请注明原文链接:https://www.cnblogs.com/ysmc/p/18796964</p><br><br>
来源:https://www.cnblogs.com/ysmc/p/18796964
頁:
[1]