C# 筆記:從 Lambda 表示式到 LINQ
Michael Tsai
1/30/2009
之前的<C# 筆記:重訪委派--從 C# 1.0 到 2.0 到 3.0>已經交代過 lambda expressions 的語法及其來龍去脈,這篇筆記會先複習一下 lambda 表示式,然後進入 LINQ。
<system.net> <defaultProxy> <proxy bypassonlocal="true" usesystemdefault="false" proxyaddress="http://proxy.xxx.com:3128" /> </defaultProxy> </system.net>
2009-02-24 更新:
書籍已出版,詳情請看《物件導向分析設計與應用 第三版》書籍相關資訊
本文原刊載於 .NET Magazine 中文版 2005 年 12 月號,現在將此舊文重發,只是為了方便參考。
首先必須先了解的是 Windows 表單(即 Form 類別)與執行緒之間有什麼關係,以及它們的運作方式,由於 Windows 表單實際上是基於傳統的 Win32 視窗技術以及訊息傳遞的機制,因此這個小節筆者就先從表單與 Win32 視窗的關係談起,請讀者注意在本節當中的表單(form)跟視窗(window)這兩個名詞所代表的是不同的東西。
當您建立一個新的表單(form)時,只不過是建立了一個.NET物件,其實這時候並沒有建立任何視窗,這個表單物件的建構函式所執行的工作只是初始化物件的內部資料(欄位成員、事件處理常式),如此而已。我們所看到的視窗(Win32視窗)則是在表單第一次顯示時才會真正建立起來,整個過程是這樣的:在設定表單的 Visible 屬性為 True 時,該屬性的 set 方法會呼叫 SetVisibleCore 方法,此方法會讀取表單的 Handle 屬性,而 Handle 屬性的 get 方法會檢查視窗是否已經建立,若已建立,就傳回視窗handle,否則便呼叫 CreateHandle 方法,視窗便是在這個時候建立起來的。程式碼列表1是筆者利用 Reflector 工具反組譯 .NET Framework 的結果,讀者可以對照剛才的描述,應該能更清楚了解整個建立視窗的過程。
1: // Form 類別的 SetVisiblCore 方法.
2: public class Form : ContainerControl
3: {
4: protected override void SetVisibleCore(bool value)
5: {
6: // ....省略其他不相關的程式碼
7:
8: UnsafeNativeMethods.SendMessage(new HandleRef(this, base.Handle), 0x18, value ? 1 : 0, 0);
9: // 上面這行在讀取 base.Handle 時,就會呼叫 Control 類別的 Handle 屬性的 get 方法(見下方)。
10: }
11: }
12:
13: // Control 類別的 Handle 屬性.
14: public class Control : Component
15: {
16: public IntPtr Handle
17: {
18: get
19: {
20: if (this.window.Handle == IntPtr.Zero) // 若視窗 handle 尚未建立
21: {
22: this.CreateHandle(); // 建立視窗 handle
23: }
24: return this.window.Handle;
25: }
26: }
27: //...省略其他成員
28: }
static void Main()所有建立視窗的動作就是從程式碼列表1裡面的Application.Run() 開始。Application.Run() 會啟動一個訊息迴圈(message pump),在這個訊息迴圈裡面,會呼叫Win32 API的GetMessage函式,以便從執行緒的訊息佇列裡面取出下一個視窗訊息,接著再呼叫DispatchMessage函式將訊息傳送給目標視窗。簡言之,由主執行緒所建立的訊息迴圈會不斷從主執行緒的訊息佇列提取視窗訊息,並發送至應用程式中的各個視窗(或控制項)。請注意「執行緒的訊息佇列」這句話,如果視窗建立在另一條執行緒,那麼它的訊息就會被送到該執行緒的訊息佇列裡。Windows表單應用程式的視窗訊息處理的過程如圖1所示,當使用者按下「測試」按鈕時,系統會將這個按鈕事件轉換成一個滑鼠點擊的訊息存放到執行緒的訊息佇列中,應用程式則藉由訊息迴圈從訊息佇列中取出訊息,並且分派到對應的控制項(此例的「測試」按鈕),以便進行對應的處理(例如:將按鈕畫成凹陷的樣子)。
{
Application.Run(new Form1());
}
1: using System.Threading;
2: //....
3:
4: private void btnTestThread_Click(object sender, System.EventArgs e)
5: {
6: // 為表單所在的執行緒取個名字,方便判斷程式執行於哪個執行緒。
7: Thread.CurrentThread.Name = "主執行緒";
8:
9: // 建立並且啟動工作執行緒。
10: Thread workerThread = new Thread(new ThreadStart(ThreadMethod));
11: workerThread.Name = "工作執行緒";
12: workerThread.Start();
13:
14: // 等待 workerThread 執行完畢。
15: workerThread.Join();
16: }
17:
18: private void ThreadMethod()
19: {
20: // 延遲一下,假裝執行了一項費時的工作。
21: Thread.Sleep(1000);
22:
23: // 更新 UI。
24: UpdateUI();
25: }
26:
27: private void UpdateUI()
28: {
29: label1.Text = Thread.CurrentThread.Name + ": Work is done!" ;
30: }
1: private void btnTestThread_Click(object sender, System.EventArgs e)
2: {
3: // 為表單所在的執行緒取個名字,方便判斷程式執行於哪個執行緒。
4: Thread.CurrentThread.Name = "主執行緒";
5:
6: Thread workerThread = new Thread(new ThreadStart(ThreadMethod));
7: workerThread.Name = "工作執行緒";
8: workerThread.Start();
9:
10: // 讓程式空轉,等待 workerThread 執行完畢。
11: while (workerThread.ThreadState != ThreadState.Stopped)
12: { }
13: }
1: private void btnTestThread_Click(object sender, System.EventArgs e)
2: {
3: // 為表單所在的執行緒取個名字,方便判斷程式執行於哪個執行緒。
4: Thread.CurrentThread.Name = "UI thread";
5:
6: Thread workerThread = new Thread(new ThreadStart(ThreadMethod));
7: workerThread.Name = "工作執行緒";
8: workerThread.Start();
9:
10: while (workerThread.ThreadState != ThreadState.Stopped)
11: {
12: // 空轉等待 workerThread 執行完畢
13: }
14: }
15:
16: private void ThreadMethod()
17: {
18: // 延遲一下,假裝執行了一項費時的工作。
19: Thread.Sleep(1000);
20:
21: // 安全地更新 UI
22: if (this.InvokeRequired)
23: {
24: MethodInvoker mi = new MethodInvoker(this.UpdateUI);
25: this.BeginInvoke(mi, null);
26: }
27: else
28: {
29: UpdateUI();
30: }
31: }
32:
33: private void UpdateUI()
34: {
35: label1.Text = Thread.CurrentThread.Name + ": Work is done!" ;
36: }