---
title: "反射"
date: "2026-04"
updated: "2026-09"
category: "設計模式與架構"
tags: ["設計模式與架構"]
description: "反射在有些地方看上去有點像是委託，但兩者的差異還是蠻大的。委託的關鍵是編譯器安全，而反射則是犧牲了編譯時的安全檢查，而帶來了執行時的靈活性：它通過字串來呼叫或取到指定的物件..."
source: "https://enriquemark.com/zh-hant/posts/reflection/"
---

反射在有些地方看上去有點像是[委託](/zh-hant/posts/delegates/)，但兩者的差異還是蠻大的。委託的關鍵是編譯器安全，而反射則是犧牲了編譯時的安全檢查，而帶來了執行時的靈活性：它通過字串來呼叫或取到指定的物件，而可以讓程式在執行時獲取或修改任何程式碼內容——本質上來說，反射是一種「元操作」，用程式碼來操作程式碼，也是字面意義上的「反射」。

就拿我之前寫的排程場景來說：假設我的排程是子類繼承，下級只關心具體的job，但如果我的場景是判斷有多少個job繼承了基類——目前的做法是用反射去掃。在我剛瞭解到委託機制後，我的設想是，能不能在基類裡面採用生命週期管理的模式，給子類包在一個run裡，排程實際執行的是run，然後基類定義一個事件，這個run就是——到這一步時，我就突然發現了——事實上在基類裡預定義的委託屬性，在每次被繼承並例項化時，這個屬性都會重置，那這樣的話事件佇列就空了。

因此啟動時直接掃描程式本身，去確認有多少子類繼承了這個基類，才能交給排程框架去統一註冊。否則要知道job有多少，就得先獲取job，要獲取job就得先註冊，註冊就得知道到底有多少job——最終就是沒有任何job會被註冊。例項化之前框架就得知道有幾個，那我不可能在這之後才讓job進事件，所以必須在啟動前就知道，那這個只能靠反射去掃有多少類繼承了base。這是一個型別發現問題，而不是委託所承載的物件之間的互動（訂閱者通過釋出者暴露的委託來操作）問題。

順帶一說，反射有一個地方在用法上跟泛型很像，而且更加靈活。比如在編譯期，我可以完全不指定型別，反而是通過執行時給什麼就接什麼的形式來承接型別。但這裡有代價，如果我寫 `where T : IConverter` ，那好歹是規定了介面的結構到底啥樣，寫的有問題，編譯期間就會報錯。而反射的話，我只能進行型別轉換——那就是我和你約定，這裡需要是這個型別，但你到底是不是真用這個型別，我是沒法硬性限制的（下面的介面來自 [控制反轉](/zh-hant/posts/inversion-of-control/)）：

```C#
Type type = Type.GetType("MyApp.XmlConverter");
IConverter converter = (IConverter)Activator.CreateInstance(type);
// 如果 XmlConverter 根本沒實現 IConverter，這裡直接 InvalidCastException
```

這裡萬一給進來的就是不符合約定，那只有執行的時候才知道錯誤。但如果我用的是泛型，那編譯期間就會直接報錯提示，根本就不會到最後階段。因此反射其實多少有了點動態語言的味道——就跟python一樣，不到執行的時候你是不知道會不會報錯的。這也是靈活性和穩健性的權衡，在有些場景下確實需要靈活性，但必須明確的知道其代價和邊界。如果可以的話，提前在這種靈活性上做好錯誤封裝：就算結果不可預料，也要控制損失範圍，使其盡可能最小化。
