注入之森裡的嚮導 - Autofac
寫程式有時很像在蓋房子。剛開始是一間小木屋,只有幾個房間,想要新增功能時,就直接在牆上多開一扇門或窗,簡單粗暴。
可隨著專案變大,房間越來越多、走道越來越複雜,你會發現:
- 有些門只接受某一把特殊的鑰匙(類別直接依賴具體實作,難以替換)。
- 有些門打不開,因為它需要另一個房間的鑰匙,而那個房間又反過來要依賴這個(循環依賴)。
- 有些地方明明只是臨時搭的走道,卻意外成了主要通道,導致一改就全系統崩潰。
這些問題,就是物件之間錯綜複雜的依賴關係。當專案還小的時候,我們可能不會覺得有什麼大不了;但當專案逐漸長成一座龐大的建築,沒有良好的規劃,就會迷路、卡關,甚至動一根樑就牽動全局。
這時候,一個好的 IoC 容器,就像是「建築師」。它幫你畫清楚藍圖,規範房間與房間之間該怎麼互通,並且在需要的時候,自動幫你準備好正確的鑰匙。
🪵 好的 IoC 容器
我們期望一個好的 IoC 容器要能做到
- 在大型專案中不會亂拋錯
- 易於使用,學習成本不能太高,以利維護
- 功能強大,能支援多種注入方式(建構式、屬性、方法)
- 錯誤提示友善,找錯誤不能像踩地雷一樣
- 生命週期管理靈活,可以細緻地控制物件生命週期
- 相容性強,能整合 ASP.NET Core、Web API…
參考Autofac筆記 1 列出一些情境
🪵「註冊遞迴錯誤處理」
假設不小心寫出:
1 | class A |
這就會變成一個無窮遞迴的相依性(A 要 B、B 又要 A)。
有些 IoC 容器遇到這種情況會怎樣?
- Autofac:會在註冊時或 resolve 時直接告訴你「你有循環依賴」,提示清楚。
- StructureMap、Ninject:早期版本可能會陷入 runtime 才崩潰,debug 地獄
- Unity:錯誤訊息難懂。
- Spring.NET:錯誤在 XML 配置裡,不好 trace。
✅ Autofac 的錯誤處理明確且提前發現,有效避免開發踩雷。
🪵 建構式選取
在類別中寫了多個 constructor:
1 | public class MyService |
那 IoC 容器該選哪一個?錯選了可能就注入不到東西,或者拋錯!
- Autofac:能根據參數完整性自動選擇最合適的 constructor,或者你可以手動指定。
- Unity:曾經會亂選或報錯,導致硬性加 [InjectionConstructor]
- StructureMap:選擇機制比較死板,無法彈性控制
- Ninject:穩定性有待加強,有時會選錯或 fail silently
✅ Autofac 對 constructor 的選擇機制既自動又可以自訂,安全又彈性。
🪵 Property 注入支援
1 | public class MyService |
這種注入方式叫 Property Injection,有時你不想在 constructor 塞太多參數時會這樣用。
- Autofac:支援良好,只要設定 .PropertiesAutowired() 就行
- Unity、Ninject:支援不完全,有些情況不會自動注入
- StructureMap:需要額外設定才會啟用
✅ Autofac 對於非建構式注入支援更全面,降低耦合度的同時提升彈性。
🪵 學習曲線太陡
Spring.NET 功能超強,根本是 Java Spring 的翻版,但使用者要用 XML 寫一堆設定:
1 | <object id="orderService" type="MyApp.Services.OrderService, MyApp"> |
這種方式很麻煩,容易錯、難 trace、維護成本高。
✅ Autofac 用純 C# 註冊,不用 XML,易學易懂,也方便 refactor。
🪵 穩定性 & 社群支援
Ninject 曾經很流行,但後來長期沒有更新,導致出現相依套件版本衝突並且在 .NET Core 時代支援落後,相比之下,Autofac 一直活躍維護且更新速度快、社群大,StackOverflow 解法多
與 ASP.NET Core 相容性強,還提供 Autofac.Extensions.DependencyInjection 套件直接整合
🪵 大量使用 C# 的 Lambda / Fluent API 設計風格
Autofac 的註冊方式長得像這樣:
1 | builder.RegisterType<MyService>() |
- Lambda(像 .OnActivated(e => …))
- Fluent API(像 .As
().WithParameter().OnActivated()) - 鏈式語法 讓程式碼簡潔、直觀、可讀性高
Autofac 特別擅長運用 Fluent API 和 Lambda 表達式來提供高可讀性且彈性的注入配置,這讓 C# 開發者可以用熟悉的語法風格完成複雜的依賴註冊。
Autofac 像是提供你一支 自動排序、自動對齊、會說話的筆,而且你用的方法就是你平常習慣的寫字方式。Unity 或 Ninject 提供的是一般的筆,能寫沒錯,但要你自己對齊、標註格式。Spring.NET 給你一張紙,要你用鉛筆慢慢描(XML) 😅
🪵 術語
Component
Component 指的是你要註冊進 IoC Container 裡的類別,也就是「要被建立、被注入」的那個物件。
Component 是提供某種功能的「服務提供者」。它實際上是一個 C# 類別(通常會實作某個介面)。
Container 會記得這個 Component,然後在別人需要它的時候負責建立它的實體。
就像公司裡有一個「會計部門」是個 Component,Container 就像是秘書,安排要找誰來做帳。
🪵 Service
Service 是指 Component 提供的「功能」,通常是透過一個 Interface 定義的。Service 是「你希望別人能使用的功能的抽象定義」。Component 實作 Service,也就是「某人能提供這個服務」。
🪵 Autowiring
Container 自動分析 Component 的建構式、屬性或方法參數,自動幫你注入所需的相依服務,這個功能稱為 Autowiring。它會自動比對建構式參數的型別,看誰能提供這些服務,然後自動塞進去。減少你手動指定依賴的工作,程式碼更簡潔。
當你跟 IoC Container 說:「我要 A」時,它會做以下事情:
查看 A 的建構式(Constructor)需要什麼參數(例如 B 和 C)。再去找有沒有已經註冊的 B 和 C。如果有,就自動幫你建立 B 和 C,然後把它們傳進 A 的建構式。如果 B 或 C 也依賴別人(像 D、E),也會一層一層往下解決。最後幫你把完整的 A 建出來,並回傳給你。這整個過程就是 Autowiring(自動接線、自動解析相依性)。
🪵 Transient Component
每次你向 Container 要一個服務時,Container 都會建立一個全新的實體給你,這就是 Transient。跟 Singleton 相反,Transient 不會重複使用實例。適合用在「無狀態」或「每次都要不同資料」的情境。
1 | builder.RegisterType<MyService>().As<IMyService>().InstancePerDependency(); // Autofac 的 Transient |
🪵 Automatic Registration
Container 自動掃描整個組件或專案中的類別,自動找出哪些可以註冊為 Component 的類別,不需你一行一行手動註冊。透過 Reflection 或 Convention 自動將類別註冊進 Container。
避免重複的樣板程式碼,尤其是大型專案會大量使用。
1 | builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) |
🪵 CommonServiceLocator
一個用來「統一介面」存取各種 IoC 容器的抽象層,讓你用同一組 API 操作不同的 IoC 容器(像 Unity、Autofac、Castle…)。這是 IoC 容器的抽象層,就像開發時寫 IDbConnection 而不是直接用 SqlConnection。讓程式不要綁死在特定 IoC 套件上,方便未來替換。就像你使用「提款機標準介面」,不管後面是台新、國泰還是富邦的銀行都能提款,因為操作介面一樣。
🪵 實作演示
在開發程式時,物件與物件之間通常會互相依賴(例如 A 物件要使用 B 物件),這種情況下如果每個物件都自己「new」出它要的東西,整個程式的耦合度(coupling)會很高、測試變得困難、擴充性也差。
1 |
|
如果撰寫自己在使用的小程式是感覺不出它的好處的,若是維護大型專案就會面對許多挑戰,這也是位甚麼我們需要如此大費周章地先把系統建構好
問題 / 挑戰 | 解決方式(為何用 Autofac) |
---|---|
物件之間的依賴太多層 | Autofac 幫你解決誰要先被 new,誰需要參數 |
要測試不同情境很麻煩 | 只要改註冊就可以換掉實作,例如從 PrintingNotifier 換成 EmailNotifier |
程式耦合度高,不易維護 | 使用介面 + DI,可以讓你更換、擴充都很輕鬆 |
建構太多重複的物件 | Autofac 幫你管理物件生命周期 |
難以追蹤誰負責釋放資源 | 用 using 包住 Container,Autofac 幫你釋放需要釋放的資源(實作了 IDisposable 的物件) |
🪵 結語
從小木屋到摩天大樓,每個系統都會經歷一段「成長痛」。
當依賴開始纏繞,當修改變得危險,我們需要的不只是更強壯的牆,而是更聰明的設計。
Autofac 就像是那位在背後默默規劃的建築師:
- 它幫你確保每個房間都有正確的入口(依賴自動解析)。
- 它幫你預留擴充的空間(介面導向、可替換的實作)。
- 它幫你管理建材的循環使用(生命週期管理)。
所以,與其說 Autofac 是個 IoC 容器,不如說它是一套 讓專案能健康長大的建築藍圖。
當我們回頭看自己搭建過的系統,就會發現:能安心專注在功能與價值,而不用每天被耦合與錯誤追著跑,本身就是一種溫暖。