澳银投资 發表於 2025-3-27 22:01:00

.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&lt;EmailService&gt;<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&lt;IEmailService, SmtpEmailService&gt;<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>&lt;ILogger&gt;(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>&nbsp;</h3>
<h3>&nbsp;</h3>
<h3>&nbsp;</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&lt;IMessageService, SmsService&gt;<span style="color: rgba(0, 0, 0, 1)">();
services.AddTransient</span>&lt;IMessageService, EmailService&gt;<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&lt;IMessageService&gt;(<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&lt;&gt;), <span style="color: rgba(0, 0, 255, 1)">typeof</span>(Repository&lt;&gt;<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&lt;IService&gt;(sp =&gt;
    <span style="color: rgba(0, 0, 255, 1)">new</span> Service(sp.GetRequiredService&lt;IOtherService&gt;()));</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&lt;IMyService&gt;();</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&lt;AppDbContext&gt;(options =&gt;<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&lt;IOrderService, OrderService&gt;<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&lt;IDomainEventDispatcher, DomainEventDispatcher&gt;<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&lt;EmailSettings&gt;(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&lt;EmailSettings&gt;<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&lt;ProductService&gt;<span style="color: rgba(0, 0, 0, 1)"> _logger;
   
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> ProductService(ILogger&lt;ProductService&gt;<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>&lt;ContainerBuilder&gt;(builder =&gt;<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&lt;IDataService, DataService&gt;<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&lt;IDataService, CachingDataService&gt;();</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&lt;IMessageService, EmailService&gt;<span style="color: rgba(0, 0, 0, 1)">();
services.AddKeyedTransient</span>&lt;IMessageService, SmsService&gt;(<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&lt;IMessageService&gt;<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&lt;IMessageService&gt;(<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]
查看完整版本: .NET 依赖注入深入详解