寫程式有時很像在蓋房子。剛開始是一間小木屋,只有幾個房間,想要新增功能時,就直接在牆上多開一扇門或窗,簡單粗暴。
可隨著專案變大,房間越來越多、走道越來越複雜,你會發現:

  • 有些門只接受某一把特殊的鑰匙(類別直接依賴具體實作,難以替換)。
  • 有些門打不開,因為它需要另一個房間的鑰匙,而那個房間又反過來要依賴這個(循環依賴)。
  • 有些地方明明只是臨時搭的走道,卻意外成了主要通道,導致一改就全系統崩潰。

這些問題,就是物件之間錯綜複雜的依賴關係。當專案還小的時候,我們可能不會覺得有什麼大不了;但當專案逐漸長成一座龐大的建築,沒有良好的規劃,就會迷路、卡關,甚至動一根樑就牽動全局。

這時候,一個好的 IoC 容器,就像是「建築師」。它幫你畫清楚藍圖,規範房間與房間之間該怎麼互通,並且在需要的時候,自動幫你準備好正確的鑰匙。

🪵 好的 IoC 容器

我們期望一個好的 IoC 容器要能做到

  • 在大型專案中不會亂拋錯
  • 易於使用,學習成本不能太高,以利維護
  • 功能強大,能支援多種注入方式(建構式、屬性、方法)
  • 錯誤提示友善,找錯誤不能像踩地雷一樣
  • 生命週期管理靈活,可以細緻地控制物件生命週期
  • 相容性強,能整合 ASP.NET Core、Web API…

參考Autofac筆記 1 列出一些情境

🪵「註冊遞迴錯誤處理」

假設不小心寫出:

1
2
3
4
5
6
7
8
9
class A
{
public A(B b) { }
}

class B
{
public B(A a) { }
}

這就會變成一個無窮遞迴的相依性(A 要 B、B 又要 A)。

有些 IoC 容器遇到這種情況會怎樣?

  • Autofac:會在註冊時或 resolve 時直接告訴你「你有循環依賴」,提示清楚。
  • StructureMap、Ninject:早期版本可能會陷入 runtime 才崩潰,debug 地獄
  • Unity:錯誤訊息難懂。
  • Spring.NET:錯誤在 XML 配置裡,不好 trace。

✅ Autofac 的錯誤處理明確且提前發現,有效避免開發踩雷。



🪵 建構式選取

在類別中寫了多個 constructor:

1
2
3
4
5
6
7
8
public class MyService
{
public MyService() { }

public MyService(IDepA depA) { }

public MyService(IDepA depA, IDepB depB) { }
}

那 IoC 容器該選哪一個?錯選了可能就注入不到東西,或者拋錯!

  • Autofac:能根據參數完整性自動選擇最合適的 constructor,或者你可以手動指定。
  • Unity:曾經會亂選或報錯,導致硬性加 [InjectionConstructor]
  • StructureMap:選擇機制比較死板,無法彈性控制
  • Ninject:穩定性有待加強,有時會選錯或 fail silently

✅ Autofac 對 constructor 的選擇機制既自動又可以自訂,安全又彈性。



🪵 Property 注入支援

1
2
3
4
public class MyService
{
public ILogger Logger { get; set; }
}

這種注入方式叫 Property Injection,有時你不想在 constructor 塞太多參數時會這樣用。

  • Autofac:支援良好,只要設定 .PropertiesAutowired() 就行
  • Unity、Ninject:支援不完全,有些情況不會自動注入
  • StructureMap:需要額外設定才會啟用

✅ Autofac 對於非建構式注入支援更全面,降低耦合度的同時提升彈性。



🪵 學習曲線太陡

Spring.NET 功能超強,根本是 Java Spring 的翻版,但使用者要用 XML 寫一堆設定:

1
2
3
<object id="orderService" type="MyApp.Services.OrderService, MyApp">
<property name="paymentService" ref="paymentService"/>
</object>

這種方式很麻煩,容易錯、難 trace、維護成本高。
✅ Autofac 用純 C# 註冊,不用 XML,易學易懂,也方便 refactor。



🪵 穩定性 & 社群支援

Ninject 曾經很流行,但後來長期沒有更新,導致出現相依套件版本衝突並且在 .NET Core 時代支援落後,相比之下,Autofac 一直活躍維護且更新速度快、社群大,StackOverflow 解法多
與 ASP.NET Core 相容性強,還提供 Autofac.Extensions.DependencyInjection 套件直接整合



🪵 大量使用 C# 的 Lambda / Fluent API 設計風格

Autofac 的註冊方式長得像這樣:

1
2
3
4
5
builder.RegisterType<MyService>()
.As<IMyService>()
.InstancePerLifetimeScope()
.WithParameter("connectionString", "Data Source=...")
.OnActivated(e => Console.WriteLine("已建立"));
  • 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
2
3
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces();


🪵 CommonServiceLocator

一個用來「統一介面」存取各種 IoC 容器的抽象層,讓你用同一組 API 操作不同的 IoC 容器(像 Unity、Autofac、Castle…)。這是 IoC 容器的抽象層,就像開發時寫 IDbConnection 而不是直接用 SqlConnection。讓程式不要綁死在特定 IoC 套件上,方便未來替換。就像你使用「提款機標準介面」,不管後面是台新、國泰還是富邦的銀行都能提款,因為操作介面一樣。



🪵 實作演示

在開發程式時,物件與物件之間通常會互相依賴(例如 A 物件要使用 B 物件),這種情況下如果每個物件都自己「new」出它要的東西,整個程式的耦合度(coupling)會很高、測試變得困難、擴充性也差。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

class Program
{

static IQueryable<Memo> GenSomeMemos()
{
//...略...
}

//// 這是 Autofac 的「容器建造器」,你可以想像它是一張圖紙或藍圖,我們要先把「怎麼組裝各種物件的方式」定義好。
static ContainerBuilder builder;


//// 這些步驟就是在告訴 Autofac:「如果有人說他需要某個物件,你要怎麼幫他生出來?」

// 當你需要一個 MemoChecker 物件時,Autofac 要自己幫你把它「生」出來,而 MemoChecker 需要兩個參數:一個是 IQueryable<Memo>,一個是 IMemoDueNotifier,這兩個東西也是由 Autofac 來生。
builder.Register(c => new MemoChecker(
c.Resolve<IQueryable<Memo>>(),
c.Resolve<IMemoDueNotifier>()));


// 如果需要 IMemoDueNotifier,你就給他一個 PrintingNotifier。這是一種「介面導向」的設計方式,可以讓系統更容易替換實作。
builder.Register(c => new PrintingNotifier(
c.Resolve<TextWriter>())).As<IMemoDueNotifier>();

// RegisterInstance(memos):把剛剛 GenSomeMemos() 生出來的資料,直接給 Autofac 用,不用他自己 new。

//Console.Out 是用來印東西到主控台的,它是系統提供的,不是你自己 new 的,因此我們告訴 Autofac:「你可以用它,但不要幫我處理它的壽命。」(這叫做 ExternallyOwned)
builder.RegisterInstance(memos);
builder.RegisterInstance(Console.Out).As<TextWriter>().ExternallyOwned();
}

//這裡是整個 Autofac 的「運作時期」。你使用 builder.Build() 把剛剛定義的註冊全部組裝成一個可用的容器(Container),接著叫 Container 把 MemoChecker 生出來,並執行 CheckNow()。
using (var container = builder.Build())
{
container.Resolve<MemoChecker>().CheckNow();
}

如果撰寫自己在使用的小程式是感覺不出它的好處的,若是維護大型專案就會面對許多挑戰,這也是位甚麼我們需要如此大費周章地先把系統建構好

問題 / 挑戰 解決方式(為何用 Autofac)
物件之間的依賴太多層 Autofac 幫你解決誰要先被 new,誰需要參數
要測試不同情境很麻煩 只要改註冊就可以換掉實作,例如從 PrintingNotifier 換成 EmailNotifier
程式耦合度高,不易維護 使用介面 + DI,可以讓你更換、擴充都很輕鬆
建構太多重複的物件 Autofac 幫你管理物件生命周期
難以追蹤誰負責釋放資源 using 包住 Container,Autofac 幫你釋放需要釋放的資源(實作了 IDisposable 的物件)


🪵 結語

從小木屋到摩天大樓,每個系統都會經歷一段「成長痛」。
當依賴開始纏繞,當修改變得危險,我們需要的不只是更強壯的牆,而是更聰明的設計。

Autofac 就像是那位在背後默默規劃的建築師:

  • 它幫你確保每個房間都有正確的入口(依賴自動解析)。
  • 它幫你預留擴充的空間(介面導向、可替換的實作)。
  • 它幫你管理建材的循環使用(生命週期管理)。

所以,與其說 Autofac 是個 IoC 容器,不如說它是一套 讓專案能健康長大的建築藍圖。
當我們回頭看自己搭建過的系統,就會發現:能安心專注在功能與價值,而不用每天被耦合與錯誤追著跑,本身就是一種溫暖。