iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
Software Development

程式設計沒有告訴你的事:30 天破解每一個 Why系列 第 21

Framework 怎麼知道 Handler 需要什麼參數?Reflection 又是怎麼出現的?

  • 分享至 

  • xImage
  •  

昨天我們一路追到 JSON Deserialization。

假設 Client 傳來:

{"name":"Andy","age":20}

Framework 可以先取得 Request Body,再根據 Content-Type 判斷這是一份 JSON,最後把它反序列化成某個 C# Object。

例如:

JSON

User
├── Name = Andy
└── Age = 20

但做到這裡後,我突然發現還有一個問題。

Framework 怎麼知道:

這一次應該把 JSON 轉成 User?

下一個 Handler 可能需要的又不是 User。

例如某個 Handler 可能想要:

User user

另一個 Handler 可能想要:

Product product

登入功能可能需要:

LoginRequest request

甚至還可能有:

int id

string name

HttpRequest request

如果 Framework 不知道 Handler 到底需要什麼,就算它已經會解析 JSON,好像也不知道最後應該把資料變成什麼。

所以今天想理解的問題是:

Framework 到底怎麼知道 Handler 需要哪些參數?

而 Reflection 又為什麼會在這個地方出現?

以前寫 ASP.NET Core Controller 時,可能會直接寫:

CreateUser(User user)

或:

GetUser(int id)

然後 Framework 好像就知道:

這個地方需要 User。

那個地方需要 int。

甚至可以自動把 Request 裡的資料放進正確的位置。

以前我很少去想:

Framework 怎麼知道我的 Method 長什麼樣?

因為 Method 明明是我自己寫的。

Framework 事先不可能知道所有開發者未來會寫:

User

Product

LoginRequest

OrderRequest

還是其他自己建立的 Class。

也就是說,如果 Framework 可以處理這些未知的 Handler,它一定需要一種能力:

在程式執行時,去「觀察」程式本身的結構。

這就開始碰到今天的主角:

Reflection。

Reflection 是什麼?

Reflection 中文通常翻成「反射」。

第一次看到這個名字時,我其實完全看不出它在做什麼。

目前可以先把它理解成:

程式在執行期間,查看 Type、Method、Property、Parameter 等程式結構資訊的能力。

平常我們寫:

User user = new User();

是我們自己本來就知道 User 是什麼。

但 Reflection 比較像是在問:

這個 Type 叫什麼?

它有哪些 Property?

這個 Method 有幾個參數?

第一個參數是什麼 Type?

第二個參數叫什麼名字?

它回傳什麼 Type?

也就是:

程式

觀察程式本身的結構

這件事情聽起來有點抽象,但放到 Framework 裡就非常合理。

Framework 為什麼需要看 Handler?

假設有一個 Handler:

CreateUser(User user)

Framework 收到:

POST /users

Request Body 是:

{"name":"Andy","age":20}

現在 Framework 已經知道:

Route

找到 CreateUser

Body

有 JSON

但接下來它必須回答:

CreateUser 到底需要什麼?

如果透過 Reflection 發現:

Method = CreateUser

Parameters:
user → User

那 Framework 就可以知道:

原來這個 Handler 的第一個參數型別是 User。

接著才有可能:

JSON

Deserialize()

User Object

傳給 CreateUser

整個流程就接起來了。

如果下一個 Handler 需要 Product 呢?

例如:

CreateProduct(Product product)

Reflection 查到:

Parameter Type = Product

那 Framework 就可以改成:

JSON

Deserialize()

Product Object

也就是 Framework 不需要事先把所有 Model 寫死。

它可以在執行時查看 Handler 本身的定義,再決定要怎麼準備資料。

這就是 Reflection 對 Framework 很重要的其中一個原因。

Handler 不一定只有一個參數

事情當然不一定永遠只有:

CreateUser(User user)

也可能是:

UpdateUser(int id, User user)

這時 Framework 看到兩個參數:

id → int

user → User

那它就必須開始思考:

id 要去哪裡找?

User 又要去哪裡找?

假設 Request 是:

PUT /users/10

Body:

{"name":"Andy","age":20}

那可能會形成:

Route

/users/{id}

Route Value

id = 10

Body

JSON

Reflection

Handler 需要:
int id
User user

接著 Framework 才能想辦法:

10

轉成 int

id

JSON

Deserialize()

user

最後:

UpdateUser(id, user)

這時我才發現,前幾天學過的 Route、Query、Header、Body 並不是互相獨立的東西。

到了 Handler Parameter Binding 時,它們全部可能變成「參數的資料來源」。

Parameter Binding 是什麼?

前面提過 Model Binding。

現在可以再更具體一點理解。

假設 Handler 是:

Search(string keyword, int page)

Request 是:

/search?keyword=book&page=2

Framework 可以先解析 Query:

keyword → book

page → 2

再透過 Reflection 查看 Handler:

keyword → string

page → int

然後嘗試配對:

Query["keyword"]

"book"

string keyword

Query["page"]

"2"

轉成 int

int page

最後才真正呼叫:

Search("book", 2)

所以 Framework 做的事情開始變成:

查看 Handler 需要什麼

從 Request 裡找資料

轉換成正確 Type

放到正確 Parameter

執行 Handler

這已經比單純 Routing 複雜很多了。

Reflection 到底能看到什麼?

以目前需要理解的範圍,可以先知道 Reflection 能協助取得很多 Metadata。

例如:

Type 的名稱

Type 有哪些 Property

Method 的名稱

Method 有哪些 Parameters

Parameter 的名稱

Parameter 的 Type

Method 的 Return Type

甚至還可以進一步取得 Attribute 等資訊。

所以對 Framework 而言,可以想成:

Handler

Reflection

Method Metadata

例如:

Method Name = CreateUser

Parameter Count = 1

Parameter Name = user

Parameter Type = User

Return Type = HttpResponse

Framework 就可以根據這些資訊決定下一步該怎麼做。

Metadata 又是什麼?

Metadata 可以先理解成:

描述程式結構的資料。

例如一個 Method:

CreateUser(User user)

真正的程式碼是:

這個 Method 裡面到底要做什麼。

但 Metadata 描述的是:

它叫 CreateUser。

它有一個參數。

參數名字是 user。

參數 Type 是 User。

它回傳什麼 Type。

這些不是 Handler 的商業邏輯本身,而是「描述 Handler」的資訊。

Reflection 就可以讓程式在 Runtime 取得這些 Metadata。

這跟 Day6、Day7 看到的 DLL 有關嗎?

其實有。

前面研究 Compiler 和 DLL 時,我們就曾經知道:

.NET Assembly 裡不只是 IL。

還包含 Metadata。

當時可能會覺得 Metadata 很抽象。

到了今天,突然開始看到它的用途。

Compiler 建置程式時,不只產生執行所需的 IL,也會保留 Type、Method、Member 等相關 Metadata。

Runtime 與 Reflection API 才有辦法在執行期間回答:

這個 Type 有哪些 Method?

這個 Method 有哪些 Parameters?

這個 Parameter 是什麼 Type?

所以 Day6 當初只是看到:

DLL 裡面除了 IL 還有 Metadata。

Day21 終於開始理解:

Framework 可以利用這些 Metadata 做很多自動化。

原來前面的知識真的接回來了。

Framework 可以直接執行 Method 嗎?

Reflection 除了查看 Method,也可以搭配相關 API 去呼叫 Method。

概念上可以想成:

Reflection

找到 Method

準備 Parameters

Invoke

例如:

Framework 找到:

CreateUser

然後準備:

User user

最後就可以呼叫這個 Method。

所以未來一個更完整的 Framework 可能形成:

Request

Routing

找到 Handler Method

Reflection 查看 Parameters

Parameter Binding

準備 Arguments

Invoke Handler

得到 Result

HttpResponse

這個流程開始非常接近平常 Framework 真正在做的事情。

那 Delegate 和 Reflection 有什麼不同?

Day13 時,我們用過:

Dictionary<string, Action>

後來變成:

Dictionary<string, Func>

當時我們直接把可執行的 Delegate 放進 Route Table。

所以找到 Route 後:

handler()

就可以直接執行。

這種方式很簡單,而且執行也很直接。

但它有一個限制:

Framework 對 Handler 的結構知道得比較少。

例如我們直接規定:

Func

就代表:

Handler 不收參數。

而且一定回傳 HttpResponse。

如果未來希望 Handler 可以長成:

CreateUser(User user)

GetUser(int id)

Search(string keyword, int page)

那單一固定的 Func 就不夠彈性。

這時 Reflection 就可以讓 Framework:

先查看 Handler 長什麼樣

再動態準備它需要的參數

所以 Delegate 和 Reflection 不是誰取代誰。

而是在不同設計下各自有用途。

為什麼 Reflection 聽起來很強,卻不能什麼都用它?

這裡也要避免另一個誤區。

Reflection 很有彈性,但也會讓程式:

更複雜

更多錯誤可能拖到 Runtime 才發現

通常比直接呼叫更有額外成本

也更難追蹤與理解

所以真正 Framework 不會因為 Reflection 很方便,就什麼事情全部靠 Reflection。

通常會搭配:

快取

預先建立 Delegate

Expression

Source Generator

編譯期產生程式碼

或其他最佳化策略

來避免每一次 Request 都重複做昂貴的探索工作。

目前我們還不用做到這麼深。

今天只要先理解:

Reflection 提供 Framework 一種在 Runtime 觀察未知使用者程式結構的能力。

這就是它最重要的價值。

📚 官方怎麼說?

在 .NET 中,Reflection 可以用來取得 Assembly、Module、Type、Method、Property、Field 等相關資訊。

也可以取得 Method 的 Parameter 資訊,甚至在適當情況下動態呼叫 Method。

這些能力對 Framework 特別重要。

因為 Framework 在被開發出來時,不可能知道未來每一個使用者會建立什麼 Controller、Method 或 Model。

但在程式真正執行時,Framework 可以透過 Runtime Metadata 與 Reflection 取得這些資訊。

也就是:

Framework 開發時

不知道 User 寫了哪些 Handler

Application 執行時

Reflection 查看 Handler Metadata

這就提供了 Framework 很大的擴充能力。


以前的我:

Framework 好像知道 Controller 裡有哪些 Method。

現在的我:

Compiler

建立 Assembly

保留 Metadata

Runtime

Reflection

Framework 查看 Type / Method / Parameter

知道 Handler 需要什麼

所以 Framework 並不是「神奇地知道」。

而是 Runtime 裡原本就存在描述程式結構的 Metadata。

Framework 再透過 Reflection 去讀取它。

前面 Day6 學到的:

Assembly = IL + Metadata + …

到了今天終於開始有更實際的意義。


今天最大的發現是:

Framework 不只是在處理 HTTP。

它同時還在理解「我們寫的 C# 程式」。

前面的流程大多是在:

Browser

HTTP

Framework

但到了今天,多出另一個方向:

我們寫的 Handler

Metadata

Reflection

Framework

最後兩條線會合:

HTTP Request 資料

Framework

Handler Metadata

Framework

Framework 再把兩邊配起來:

Request Data

Parameter Binding

Handler Parameters

Execute

這就是為什麼 Framework 可以提供那麼高階的開發體驗。

Framework 可以利用 Runtime Metadata 和 Reflection,查看 Handler Method 的結構。

例如發現:

CreateUser(User user)

就可以知道:

這個 Handler 需要一個 User。

接著再從 Request 中找到適合的資料來源,例如 JSON Body,反序列化成 User,最後把它傳給 Handler。

也就是:

Handler Metadata
+
Request Data

Parameter Binding

Handler Execution

🛠 與 Mini Web Framework 的關聯

目前 Mini Web Framework 已經一路從:

TCP

HTTP

Request

Routing

Handler

Response

慢慢走到更高階的問題。

之前 Route Table 是:

Path

固定的 Delegate

未來則可以繼續思考:

Path

Handler Method

Reflection

Parameters

Binding

Invoke

整體可能會變成:

Browser

HttpRequest

Router

Handler Metadata

Reflection

Parameter Binding

Handler

HttpResponse

Browser

這時我們的 Mini Web Framework 就開始不只是 HTTP Server。

而是真的開始處理:

開發者寫的程式要怎麼和 HTTP 世界連接起來。

今天暫時不實作

今天一樣先把 Reflection 為什麼會出現在 Framework 裡想清楚。

因為目前 Day16~Day20 的一些 Request Parsing、Headers、Query、Body、JSON 實作還需要補上。

等前面的資料流真正跑通後,再實作 Reflection 與 Parameter Binding 才不會只剩下一段看不懂的「魔法程式碼」。

之後實作時,可以從非常簡單的實驗開始。

例如先建立一個普通 Method,再讓程式使用 Reflection 印出:

Method Name

Parameter Name

Parameter Type

Return Type

先親眼確認:

程式真的可以在 Runtime 查看另一段程式的結構。

再慢慢把它接進 Mini Web Framework。

結尾

今天第一次研究 Reflection,讓我重新理解「Framework 自動幫我做事情」這句話。

以前看到:

CreateUser(User user)

會覺得 Framework 就是知道要給我一個 User。

但現在一路拆下來後,可以看到背後可能經過:

HTTP Request

Body

JSON

另一方面:

Handler

Reflection

發現 Parameter Type = User

兩邊最後接起來:

JSON

Deserialize()

User Object

傳給 Handler

所以 Framework 所謂的「自動」,其實不是沒有規則。

它只是把:

Metadata
Reflection
Parsing
Type Conversion
Deserialization
Parameter Binding

這些步驟全部包起來了。

而開發者最後只看到:

CreateUser(User user)

這也再次符合這個系列一路以來最大的感受:

Framework 並沒有讓底層機制消失。

它只是把複雜度藏到了更適合的位置。


現在 Framework 已經有可能知道:

Handler 需要什麼參數。

但 Reflection 取得的可能只是:

Parameter Type = User

接下來 Framework 還要判斷:

User 要從 Body 取得?

id 要從 Route 取得?

keyword 要從 Query 取得?

某個值又要不要從 Header 取得?

也就是說:

知道 Handler 需要什麼,還不代表知道「資料要去哪裡找」。

所以新的問題就是:

Framework 到底怎麼決定每個參數要從 Route、Query、Header 還是 Body 取得?


上一篇
JSON 明明只是一串文字,Framework 為什麼可以把它變成 C# 物件?
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言