詳細解析 .NET 依賴注入的三種生命周期模式

在這里插入圖片描述

文章目錄

      • 一、Transient(瞬時生命周期)
        • 原理
        • 使用方式
        • 核心特性
        • 適用場景
        • 優勢
        • 劣勢
      • 二、Scoped(作用域生命周期)
        • 原理
        • 使用方式
        • 核心特性
        • 適用場景
        • 優勢
        • 劣勢
      • 三、Singleton(單例生命周期)
        • 原理
        • 使用方式
        • 核心特性
        • 適用場景
        • 優勢
        • 劣勢
      • 三、生命周期對比分析
        • 功能對比表
        • 性能基準測試
        • 典型錯誤案例
      • 四、生命周期決策樹
      • 五、最佳實踐指南
      • 六、總結

一、Transient(瞬時生命周期)

原理
客戶端DI容器請求IMyService創建新的MyService實例返回新實例再次請求IMyService再次創建新的MyService實例返回另一個新實例客戶端DI容器
使用方式
// 注冊服務
builder.Services.AddTransient<IMyService, MyService>();// 使用示例
public class ClientService
{private readonly IMyService _service1;private readonly IMyService _service2;public ClientService(IMyService service1, IMyService service2){// 兩個參數會收到不同的實例_service1 = service1;_service2 = service2;}
}
核心特性
  1. 每次請求創建新實例
  2. 不共享狀態
  3. 自動釋放(當請求處理完成時)
適用場景
  • 輕量級無狀態服務(如計算器、驗證器)
  • 需要線程隔離的服務
  • 每次操作需要全新狀態的場景
// 典型應用:數據轉換服務
public interface IDataTransformer
{string Transform(string input);
}public class ReverseTransformer : IDataTransformer
{public string Transform(string input) => new string(input.Reverse().ToArray());
}// 注冊
services.AddTransient<IDataTransformer, ReverseTransformer>();
優勢
  1. 內存安全:不會意外共享狀態
  2. 線程安全:每個線程使用獨立實例
  3. 簡單可靠:無需考慮狀態管理
劣勢
  1. 性能開銷:頻繁創建/銷毀對象
  2. 內存碎片:大量短期對象增加GC壓力
  3. 資源浪費:不適合初始化成本高的服務

二、Scoped(作用域生命周期)

原理
請求1DI容器作用域請求2S2開始請求創建作用域請求IUserRepository創建新實例返回實例A再次請求IUserRepository返回相同的實例A結束請求銷毀作用域釋放所有Scoped實例新請求創建新作用域請求IUserRepository返回新實例B請求1DI容器作用域請求2S2
使用方式
// 注冊服務
builder.Services.AddScoped<IUserRepository, UserRepository>();// ASP.NET Core 中間件中
app.Use(async (context, next) =>
{// 手動創建作用域using var scope = context.RequestServices.CreateScope();var repo = scope.ServiceProvider.GetService<IUserRepository>();await repo.LogRequestAsync(context.Request);await next();
});
核心特性
  1. 作用域內單例(同一作用域內實例共享)
  2. 跨請求隔離(不同請求不同實例)
  3. 自動釋放(作用域結束時)
適用場景
  • 數據庫上下文(如EF Core DbContext)
  • 請求級狀態管理
  • 事務處理單元
// 典型應用:EF Core DbContext
public class AppDbContext : DbContext
{public DbSet<User> Users { get; set; }
}// 注冊
services.AddScoped<AppDbContext>();// 在控制器中使用
public class UserController : Controller
{private readonly AppDbContext _context;public UserController(AppDbContext context){_context = context; // 同一請求內共享實例}
}
優勢
  1. 狀態隔離:不同請求互不影響
  2. 資源優化:重用初始化成本高的對象
  3. 事務一致性:天然支持事務邊界(整個請求)
劣勢
  1. 作用域泄漏:意外在單例中引用會導致內存泄漏
// 錯誤示例:單例中引用Scoped服務
public class SingletonService
{private readonly IUserRepository _repo; // 危險!public SingletonService(IUserRepository repo){_repo = repo; // 這會導致Scoped服務變成"偽單例"}
}
  1. 異步風險:在async/await中可能跨越不同作用域
  2. 測試復雜性:需模擬作用域環境

三、Singleton(單例生命周期)

原理
持有引用
1
DI容器
+SingletonCache
Singleton實例
+首次請求時創建
+全局唯一
使用方式
// 注冊服務
builder.Services.AddSingleton<ICacheService, CacheService>();// 預創建實例(立即初始化)
var cache = new CacheService();
builder.Services.AddSingleton<ICacheService>(cache);// 延遲初始化
builder.Services.AddSingleton<IBackgroundService>(provider => new BackgroundService(provider.GetRequiredService<ILogger>()));
核心特性
  1. 全局唯一實例(整個應用生命周期)
  2. 首次請求時創建(除非預注冊實例)
  3. 應用關閉時釋放
適用場景
  • 配置服務(如IOptions)
  • 內存緩存
  • 共享資源連接(如Redis連接池)
// 典型應用:內存緩存
public class MemoryCacheService : ICacheService, IDisposable
{private readonly ConcurrentDictionary<string, object> _cache = new();private Timer _cleanupTimer;public MemoryCacheService(){_cleanupTimer = new Timer(_ => Cleanup(), null, 0, 60_000);}public object Get(string key) => _cache.TryGetValue(key, out var value) ? value : null;public void Dispose() => _cleanupTimer?.Dispose();
}// 注冊
services.AddSingleton<ICacheService, MemoryCacheService>();
優勢
  1. 性能最佳:單次初始化,零實例化開銷
  2. 全局狀態共享:跨請求共享數據
  3. 資源集中管理:如連接池、線程池
劣勢
  1. 線程安全風險:需手動實現同步機制
public class CounterService
{private int _count = 0;// 危險:非線程安全public void Increment() => _count++;// 正確:線程安全版本public void SafeIncrement() => Interlocked.Increment(ref _count);
}
  1. 內存泄漏:意外持有引用導致GC無法回收
  2. 啟動延遲:復雜單例初始化影響應用啟動時間

三、生命周期對比分析

功能對比表
特性TransientScopedSingleton
實例創建時機每次請求作用域首次請求全局首次請求
實例數量多個每作用域一個全局一個
狀態共享范圍無共享作用域內共享全局共享
線程安全要求中等
適用場景無狀態服務請求級狀態全局共享資源
內存管理自動回收作用域結束時回收應用結束時回收
性能開銷高(頻繁創建)中等低(單次創建)
性能基準測試
BenchmarkDotNet=v0.13.1, OS=Windows 10
Intel Core i7-11800H 2.30GHz, 1 CPU, 16 cores| 方法                | 調用次數 | 平均耗時 | 內存分配 |
|---------------------|---------|----------|----------|
| TransientResolve    | 10000   | 158 ns   | 32 B     |
| ScopedResolve       | 10000   | 76 ns    | 0 B      |
| SingletonResolve    | 10000   | 38 ns    | 0 B      |
典型錯誤案例

案例1:作用域泄漏

// 錯誤:單例中注入Scoped服務
builder.Services.AddSingleton<ReportService>();
builder.Services.AddScoped<DatabaseContext>();// 解決方案1:使用工廠方法
builder.Services.AddSingleton<ReportService>(provider => new ReportService(provider.GetRequiredService<DatabaseContext>));// 解決方案2:改為作用域服務
builder.Services.AddScoped<ReportService>();

案例2:線程競爭

public class CacheService
{private Dictionary<string, object> _cache = new();// 錯誤:非線程安全public void Add(string key, object value){_cache[key] = value;}// 正確:使用并發集合private ConcurrentDictionary<string, object> _safeCache = new();public void SafeAdd(string key, object value){_safeCache[key] = value;}
}

案例3:資源未釋放

public class FileService : IDisposable
{private FileStream _fileStream;public FileService(){_fileStream = File.Open("data.bin", FileMode.Open);}// 必須實現Disposepublic void Dispose(){_fileStream?.Dispose();}
}// 注冊(Singleton需顯式釋放)
builder.Services.AddSingleton<FileService>();

四、生命周期決策樹

graph TDA[新服務注冊] --> B{是否有狀態?}B -->|無狀態| C[優先Transient]B -->|有狀態| D{狀態共享范圍?}D -->|請求級| E[選擇Scoped]D -->|應用級| F{是否線程安全?}F -->|是| G[選擇Singleton]F -->|否| H[重構為線程安全或選Scoped]C --> I{創建成本高?}I -->|是| J[考慮Scoped]I -->|否| K[保持Transient]G --> L{需要立即初始化?}L -->|是| M[預注冊實例]L -->|否| N[延遲初始化]E --> O[確保作用域邊界]G --> P[實現IDisposable]

五、最佳實踐指南

  1. 默認選擇Transient

    • 除非有明確需求,否則優先無狀態服務
    // 好:無狀態服務使用Transient
    services.AddTransient<IValidator, EmailValidator>();
    
  2. Scoped生命周期黃金法則

    • 一個請求對應一個工作單元
    services.AddScoped<OrderProcessingService>();
    
  3. Singleton安全準則

    • 實現線程安全
    • 實現IDisposable
    • 避免依賴非Singleton服務
    public class SafeCache : ICache, IDisposable
    {private readonly ConcurrentDictionary<string, object> _store;private readonly Timer _timer;private readonly ReaderWriterLockSlim _lock = new();public void Dispose(){_timer?.Dispose();_lock?.Dispose();}
    }
    
  4. 生命周期驗證

    // 啟用容器驗證
    var provider = services.BuildServiceProvider(validateScopes: true);
    
  5. 混合生命周期策略

    public class HybridService
    {// 長周期依賴Singletonprivate readonly ICache _cache;// 短周期依賴Transient工廠private readonly Func<ITransientService> _factory;public HybridService(ICache cache,Func<ITransientService> factory){_cache = cache;_factory = factory;}public void Process(){// 按需創建Transient實例using var service = _factory();service.DoWork(_cache.GetData());}
    }
    

在這里插入圖片描述

六、總結

  1. Transient:輕量級無狀態服務的首選,但需警惕高頻創建的性能開銷
  2. Scoped:請求敏感資源(如數據庫連接)的黃金標準,注意作用域邊界
  3. Singleton:全局共享資源的最佳載體,但必須確保線程安全和資源釋放

架構師建議:在大型系統中采用分層生命周期策略:

  • 基礎設施層(緩存、配置):Singleton
  • 領域服務層:Scoped
  • 工具類/輔助服務:Transient

定期使用 .BuildServiceProvider(validateScopes: true) 檢測生命周期錯誤,
這對預防生產環境的內存泄漏和狀態污染至關重要。

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/88051.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/88051.shtml
英文地址,請注明出處:http://en.pswp.cn/web/88051.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

軟件工程經濟與倫理

前言 各位帥哥美女&#xff0c;能看到這篇博客的都有口福了&#xff0c;學習這門課程就像遨游在大份的海洋&#xff0c;一不小心就吃上一口。能看到這篇博客說明我們是有緣人可以點贊收藏一下&#xff0c;這篇博客可以在你無比饑餓的時候給你送上一坨&#xff01;&#xff08;香…

AI 體驗走查 - 火山引擎存儲的 AI UX 探索之路

01 概述 火山引擎存儲技術團隊驅動 AI 自主完成用戶體驗走查 / 可用性測試的執行與評價&#xff0c;幫助業務改善交互體驗。 立項“故事走查”的背景訴求和 AI 機遇 如何搭建“AI 評價”能力&#xff0c;精準識別交互問題 讓交互體驗故事走查變為技術產品&#xff0c;講解系…

【世紀龍科技】汽車零部件檢驗虛擬實訓室-助力汽車職教實訓

在汽車產業加速向電動化、智能化轉型的背景下&#xff0c;職業院校汽車專業教學面臨新的挑戰&#xff1a;傳統實訓受限于設備數量不足、操作風險高、標準化程度低等問題&#xff0c;導致學生實踐機會有限&#xff0c;技能掌握不扎實。如何讓學生在有限資源下高效掌握零部件檢驗…

MySQL常用操作 查看表描述以及表結構、連接數及緩存和性能指標

查看表描述以及表結構查看數據庫名SHOW DATABASES; SELECT DATABASE(); SELECT DATABASE() AS current_database;查看數據庫中表的列表SHOW TABLES; SELECT TABLE_NAME, TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA your_database_name; SELECT TABLE_NA…

音視頻學習(三十六):websocket協議總結

概述項目描述標準RFC 6455使用端口默認 80&#xff08;ws&#xff09;&#xff0c;443&#xff08;wss&#xff09;基于協議TCP特性全雙工、低開銷、持久連接、可穿透代理特點 全雙工通信&#xff1a; WebSocket 允許客戶端和服務器之間建立一個持久的連接&#xff0c;并且數據…

docker版本nacos的搭建

1.下載鏡像2.拷貝出容器中對應的配置文件&#xff0c;logs&#xff0c;data&#xff0c;conf3.編寫yaml配置文件version: 3.8 services:nacos-server:image: nacos/nacos-server:v2.4.0container_name: nacos-serverrestart: unless-stoppedports:- "8848:8848" # …

【機器學習深度學習】 如何解決“宏平均偏低 / 小類識別差”的問題?

目錄 &#x1f9e9; 場景 一、先問清楚&#xff1a;小類差&#xff0c;到底差在哪&#xff1f; 二、對癥下藥&#xff1a;六大優化策略&#xff08;分類任務專用&#xff09; ? 1. 處理類別不平衡&#xff08;最常見&#xff09; ? 2. 優化數據質量 ? 3. 更強的模型結…

數據結構 --- 棧

棧 --- stack前言一、棧結構二、相關方法1.初始化2.入棧3.出棧4.判空5.獲取棧頂元素6.獲取棧大小7.銷毀前言 棧是一個特殊的線性表&#xff0c;遵循一個先進后出的特性&#xff0c;即操作數據&#xff08;入棧&#xff0c;出棧&#xff09;只能從棧頂操作&#xff0c;棧底是一…

【uniapp】---- 在 HBuilderX 中使用 tailwindcss

1. 前言 接手了一個uniapp的微信小程序項目,因為在上一個 taro 的項目中使用的 tailwindcss,感覺比較方便,又不想動項目中原來的代碼,因此就配置 tailwindcss,在新創建的子包中使用。 2. 分析 vue2 版本的 uni-app 內置的 webpack 版本為 4 , postcss 版本為 7, 所以還是…

Spring Boot + Easy Excel 自定義復雜樣式導入導出

tips&#xff1a;能用模板就用模板&#xff0c;當模板不適用的情況下&#xff0c;再選擇自定義生成 Excel。官網&#xff1a;https://easyexcel.opensource.alibaba.com安裝<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</arti…

Spark從入門到實戰:安裝與使用全攻略

目錄一、Spark 簡介1.1 Spark 的概念1.2 Spark 的優勢1.3 Spark 的應用場景二、安裝前準備2.1 硬件要求2.2 軟件要求2.3 下載 Spark三、Spark 安裝步驟3.1 解壓安裝包3.2 配置環境變量3.3 配置 spark-env.sh3.4 配置 slaves 文件&#xff08;分布式模式&#xff09;3.5 啟動 Sp…

Python 進程間的通信:原理剖析與項目實戰

在 Python 編程中,當涉及多進程編程時,進程間的通信(Inter-Process Communication,簡稱 IPC)是一個重要的課題。多個進程在運行過程中,常常需要交換數據、傳遞狀態或協同工作,這就離不開進程間通信機制。本文將深入講解 Python 進程間通信的原理,并結合實際項目案例,展…

神經網絡之BP算法

一、正向傳播正向傳播&#xff08;Forward Propagation&#xff09;是神經網絡中數據從輸入層流向輸出層的過程。輸入數據通過各層的權重和激活函數逐層計算&#xff0c;最終得到預測輸出。數學表示&#xff1a; 對于第 ( l ) 層的神經元&#xff0c;其輸出計算如下&#xff1a…

Ubuntu 版本號與別名對照表(部分精選)

Ubuntu 的別名遵循 形容詞 動物名 的命名規則&#xff0c;且兩個單詞首字母相同&#xff0c;按字母表順序循環使用&#xff08;從 Ubuntu 6.06 開始&#xff09;。 &#x1f4c5; Ubuntu 版本號與別名對照表&#xff08;部分精選&#xff09; 版本號別名 (開發代號)發布時間…

實驗03-Spark批處理開發

使用Spark Shell探索RDD 啟動并使用Scala Spark Shell 在終端窗口&#xff0c;啟動Scala Spark shell&#xff1a; spark-shell --master local查看對象&#xff1a; scala> sc scala> spark輸入spark.[TAB]然后可以看到所有可用的方法。 讀并顯示文本文件 查看文本…

【R語言】Can‘t subset elements that don‘t exist.

Error in select(): ? In argument: all_of(label_col). Caused by error in all_of(): ! Cant subset elements that dont exist. ? Element Label doesnt exist. Run rlang::last_trace() to see where the error occurred.原文中文解釋涉及關鍵詞Error in select()報錯發生…

Spring的依賴注入(xml)

引入 首先先明白&#xff0c;依賴注入描述的是在容器中建立bean與bean之間的依賴關系&#xff0c;本質就是將一個類中和別的類解耦的方式&#xff0c;就是把別的類&#xff0c;寫在成員變量位置&#xff0c;再對外提供可以給成員變量賦值的方法&#xff0c;外界就直接調用來給…

docker運行的一些常用命令

docker images 顯示可以加載的鏡像docker ps 顯示運行的docker容器 加-a顯示所有的容器docker run --name 容器名字 -d 鏡像名字docker start 容器名/ID 開啟容器docker stop 容器名/ID 關閉容器docker exec -it dock…

Django跨域

步驟 1&#xff1a;安裝 django-cors-headerspip install django-cors-headers步驟 2&#xff1a;修改 Django 配置 在 settings.py 中添加&#xff1a;INSTALLED_APPS [...,"corsheaders", # 新增 ]MIDDLEWARE [...,"corsheaders.middleware.CorsMiddleware…

20250706-10-Docker快速入門(下)-Harbor鏡像倉庫_筆記

一、Harbor鏡像倉庫搭建與使用1. Harbor概述&#xfeff;&#xfeff;定義: 由VMWare公司開源的容器鏡像倉庫系統技術基礎: 在Docker Registry基礎上進行企業級擴展核心特性:提供管理用戶界面(GUI)基于角色的訪問控制(RBAC)支持&#xfeff;AD/LDAP\mathrm{AD}/\mathrm{LDAP}AD…