tag:blogger.com,1999:blog-45003637539819197832024-03-12T12:53:32.361+08:00Huan-Lin 學習筆記程式設計, .NET, 閱讀, 寫作, 出版, 資訊技術Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.comBlogger580125tag:blogger.com,1999:blog-4500363753981919783.post-88602624881002587992024-02-19T00:44:00.008+08:002024-02-19T21:09:32.038+08:00GitHub Copilot 入門<p>超級 lag 的我,終於也開始用 Copilot 啦。</p><span><a name='more'></a></span><p><br /></p><blockquote><p>建議<a href="https://huanlin.cc/blog/2024/02/18/get-started-with-github-copilot/" target="_blank">至分站閱讀本文</a>(無廣告,且排版較美觀)</p></blockquote><h2 style="text-align: left;">簡介</h2><h3 style="text-align: left;">Copilot 能做什麼?</h3><p>當我們寫程式的時候,Copilot 會在背後忙著推測我們的意圖,並適時提供它建議的程式碼。</p><p><br /></p><p>除了幫我們產生程式碼(包括單元測試、SQL 查詢等等),Copilot 還能夠:</p><p></p><ul style="text-align: left;"><li>解釋程式碼(產生註解)。</li><li>重新組織程式碼,使其更易讀易懂。</li><li>自動找出程式中的 bug 並給予修正。</li></ul><p></p><p>亦可參考官網的展示動畫來了解 Copilot 的功能:<a href="https://github.com/features/copilot">https://github.com/features/copilot</a>。</p><h3 style="text-align: left;">如果沒有 Copilot...</h3><p>如果沒有 Copilot 這類工具,我們寫程式的時候如果想要借助搜尋引擎或 AI 來找答案,通常會至少開兩個視窗:一個用於編寫程式碼,另一個用於詢問 Google 或 ChatGPT,或者其他網路論壇、文件等等。</p><p><br /></p><p>在比較古早的年代,上述做法大致堪用。但以目前的生態環境,開發人員同時要會兩三種甚至更多種程式語言,已是司空見慣,而且程式語言的語法不斷演進和增加,再加上各式各樣的 API、SDK、類別庫等用法,如果沒有 Copilot 隨時提供提示,光是查 Google 或者問 ChatGPT,生產力恐怕很難跟得上別人。</p><h2 style="text-align: left;">啟用 Copilot</h2><p>Copilot 是 GitHub 提供的訂閱制 AI 服務,逐月或逐年收費。如果要啟用 Copilot 服務,需要先登入 GitHub 帳號,然後開啟以下網址便可查看自己帳戶的 Copilot 服務狀態:</p><p><br /></p><p><a href="https://github.com/settings/copilot">https://github.com/settings/copilot</a></p><p><br /></p><p>如果你的 GitHub 帳戶尚未訂閱 Copilot 服務,看到的頁面會像這樣:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirLyanMZp747NpHoytUrUjjn3gyEOQVIK19zJhqbfvYmXsl5uF5W8-PMGTEH6Kg6LBeKucgOMmjD_kCca2_EyEyDXZyEATg10fslRbwvqrmFQyHtzDUnda7Isx5Mf_qYEwGa2uXmsiajsKoSAPfFwtWSJ11iaCBmL0z79VD4DPmxyZby1K1Q4shgjB8yg/s1166/copilot-not-activated.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="659" data-original-width="1166" height="362" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirLyanMZp747NpHoytUrUjjn3gyEOQVIK19zJhqbfvYmXsl5uF5W8-PMGTEH6Kg6LBeKucgOMmjD_kCca2_EyEyDXZyEATg10fslRbwvqrmFQyHtzDUnda7Isx5Mf_qYEwGa2uXmsiajsKoSAPfFwtWSJ11iaCBmL0z79VD4DPmxyZby1K1Q4shgjB8yg/w640-h362/copilot-not-activated.png" width="640" /></a></div><br /><p>點擊 Start free trial 按鈕可以免費試用 30 天,但啟用之前必須先輸入你的信用卡資料。試用期限結束後,便會自動從你的信用卡扣款。</p><blockquote><p>為何 Copilot 要收費?因為它背後所倚賴的 GPT3 模型的訓練成本很高。</p></blockquote><p><br /></p><p>若已經訂閱,頁面看起來會類似以下截圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikmIn8DfWvPoFPq6doCr130K-QJSYNNXXJ4SwoKNo8mL_q6P6bDFKChdNHD7PbkM2l4SvYBcxyuru3NOpYLg7TapI6nUAwEClOURTUABzaWIuXwjDzMZiXtifyHylJW7jimgfldGUaZs9gJvgJZuD0qJ2wDpGG61_Ias4a8SS4-yz26pBKkgagJvtqwkM/s1536/copilot-activated.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="771" data-original-width="1536" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikmIn8DfWvPoFPq6doCr130K-QJSYNNXXJ4SwoKNo8mL_q6P6bDFKChdNHD7PbkM2l4SvYBcxyuru3NOpYLg7TapI6nUAwEClOURTUABzaWIuXwjDzMZiXtifyHylJW7jimgfldGUaZs9gJvgJZuD0qJ2wDpGG61_Ias4a8SS4-yz26pBKkgagJvtqwkM/w640-h322/copilot-activated.png" width="640" /></a></div><br /><p><br /></p><p>點左邊面板的 Billing and plans,展開的選單可讓你查看你的帳單資訊、管理用量限制、設定付款方法等等。</p><p><br /></p><p>啟用 Copilot 訂閱服務之後,便可以開始在 VS Code、Visual Studio 等 IDE 工具中安裝 Copilot 擴充功能,然後開始體驗寫程式的時候有 AI 助手隨時從旁協助的威力。</p><p><br /></p><p>在開始之前,有一個小提醒(你大概早就知道了):Copilot 提供的程式碼不見得符合你當下的意圖、程式語法不一定完全正確。你也可能會發現,有時候它提供的程式碼雖然能 work,但使用了比較老舊的語法。</p><p><br /></p><p>總之:不要完全信任 Copilot。</p><p><br /></p><h2 style="text-align: left;">安裝 Copilot 擴充功能</h2><p>Copilot 官方文件提供了 VS Code、Visual Studio、JetBrains 等 IDE 的安裝說明,可由此進入:</p><p><br /></p><p><a href="https://docs.github.com/en/copilot/using-github-copilot/getting-started-with-github-copilot" target="_blank">Getting started with GitHub Copilot</a></p><p><br /></p><p>然後從頁面上方選擇你使用的 IDE。</p><p><br /></p><p>我在 VS Code 和 Visual Studio 都安裝了以下擴充功能:</p><p></p><ul style="text-align: left;"><li><a href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot" target="_blank">GitHub Copilot</a></li><li><a href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat" target="_blank">GitHub Copilot Chat</a></li></ul><p></p><p><br /></p><p>我的 Visual Studio 還有安裝 <a href="https://marketplace.visualstudio.com/items?itemName=jefferson-pires.VisualChatGPTStudio" target="_blank">Visual chatGPT Studio extension</a>。</p><h3 style="text-align: left;">For Visual Studio</h3><p>如果是 Visual Studio,有兩個使用須知:</p><p></p><ul style="text-align: left;"><li>必須是 Visual Studio 2022 17.6 或之後的版本才有支援 Copilot。</li><li>在 Visual Studio 中安裝好 Copilot extension 之後,還必須加入你的 GitHub 帳號:從主選單點擊 <b>File > Account Settings...</b>。詳細步驟請參考:<a href="https://learn.microsoft.com/en-us/visualstudio/ide/work-with-github-accounts?view=vs-2022#adding-public-github-accounts" target="_blank">Adding public GitHub accounts</a></li></ul><p></p><p><br /></p><p>如果一切安裝設定妥當,在 Visual Studio 編輯器中敲程式碼的時候,Copilot 便會開始工作。</p><p><br /></p><p>Copilot 建議的程式碼會以灰色字體呈現,此時按 Tab 鍵即可接受建議。我們也可以從 Output 視窗中查看它是否真的有在工作。如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhojAxkFGbpvZ7IVJdTdHIoiC09BGFFAvzUXHKV06E1COsmDZmXljYIy8LGTqJGFv38MVv6Ib7P2l-386TSWoeemxzeiawQMJPcwOtYCqvZCVSFbeVQtFjSgQw9K3-H_tEFHxbQKM113YGPAnQnpyATCK5J46pXrGEB2hsEJ670N2SWuzf5IEms9rL38SQ/s1177/vs-output-copilot.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="626" data-original-width="1177" height="340" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhojAxkFGbpvZ7IVJdTdHIoiC09BGFFAvzUXHKV06E1COsmDZmXljYIy8LGTqJGFv38MVv6Ib7P2l-386TSWoeemxzeiawQMJPcwOtYCqvZCVSFbeVQtFjSgQw9K3-H_tEFHxbQKM113YGPAnQnpyATCK5J46pXrGEB2hsEJ670N2SWuzf5IEms9rL38SQ/w640-h340/vs-output-copilot.png" width="640" /></a></div><p><br /></p><blockquote><p>備註:我的 Visual Studio 還有安裝 <a href="https://marketplace.visualstudio.com/items?itemName=jefferson-pires.VisualChatGPTStudio" target="_blank">Visual chatGPT Studio extension</a>。 </p></blockquote><h2 style="text-align: left;">測試看看</h2><p>在 VS Code 或 Visual Studio 中編輯一個 C# 程式檔案,輸入以下註解:</p><p><br /></p><p><span face="SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace" style="background-color: white; color: green; font-size: 14.4px; white-space: pre;">// Output "Hello, World!" to the screen</span></p><p><br /></p><p>或者用中文也可以:</p><p><br /></p><p><span face="SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace" style="background-color: white; color: green; font-size: 14.4px; white-space: pre;">// 輸出 "Hello, World!" 至螢幕</span></p><p><br /></p><p>當你敲下最後一個字,按 Enter 之後(插入新行),Copilot 應該就會在下一行出現建議的程式碼:</p><p><br /></p><p><span face="SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace" style="background-color: white; color: #212529; font-size: 14.4px; white-space: pre;">Console.WriteLine(</span><span face="SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace" style="background-color: white; box-sizing: border-box; color: #a31515; font-size: 14.4px; white-space: pre;">"Hello, World!"</span><span face="SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace" style="background-color: white; color: #212529; font-size: 14.4px; white-space: pre;">);</span></p><p><br /></p><p>一旦確定 Copilot 能正常工作,之後就可以快樂使用了。(三不五時可能要登入 GitHub 網站查看用量和費用)</p><p><br /></p><p>當你在 VS Code 編輯器中敲入文字時,如果看到如下圖的星星圖案,可以用滑鼠點一下來打開 inline chat 文字框,然後對 Copilot 下達指令。</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnHkFYmWFVxuPRHg0K8B4z734pFKhp0y-lzpQSmiINjGp-5OnOOwVeAEUSO-3fh_P7tZRdafc9fqA6t9BToFLdRlyNtr-FO75YybktICXRFX6pIEiCaNGEHLat2OuLNrtKpuSWwlCXOfqyVd_ZFd0IpYUmEgK-ulumlNbpWBu4yAundfGKJQBB5KXdHjg/s898/vscopilot-hint.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="314" data-original-width="898" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnHkFYmWFVxuPRHg0K8B4z734pFKhp0y-lzpQSmiINjGp-5OnOOwVeAEUSO-3fh_P7tZRdafc9fqA6t9BToFLdRlyNtr-FO75YybktICXRFX6pIEiCaNGEHLat2OuLNrtKpuSWwlCXOfqyVd_ZFd0IpYUmEgK-ulumlNbpWBu4yAundfGKJQBB5KXdHjg/w640-h224/vscopilot-hint.png" width="640" /></a></div><br /><p>這個 inline chat 功能是由 GitHub Copilot Chat extension 提供。你也可以從 VS Code 的 Activity Bar 開啟 Chat 面板,如下圖所示:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWzNHo3gnIK_t2R2o9rakfaUVwqvcYZG30kYsZSKrPNjCiTYMH1ldassfAH921CAQSboU5Afft070TOfrwSA9TxSv6Z_WneG1_PDKZ6QSTk2OxZhQYWb4cic52C29NlkWXsR1fRSPVBovFeRREeg8Wumu4ft84b-rn7bInytpUJSK83yrBmuEVs9COOLE/s1425/vscode-copilot-chat.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="877" data-original-width="1425" height="394" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWzNHo3gnIK_t2R2o9rakfaUVwqvcYZG30kYsZSKrPNjCiTYMH1ldassfAH921CAQSboU5Afft070TOfrwSA9TxSv6Z_WneG1_PDKZ6QSTk2OxZhQYWb4cic52C29NlkWXsR1fRSPVBovFeRREeg8Wumu4ft84b-rn7bInytpUJSK83yrBmuEVs9COOLE/w640-h394/vscode-copilot-chat.png" width="640" /></a></div><p><br /></p><h2 style="text-align: left;">結語</h2><p></p><ul style="text-align: left;"><li>Copilot 強大好用,能大幅提高生產力。</li><li>開發人員還是必須有程式語言的基礎,並給予更具體、精確的提示。</li><li>不能完全信任 Copilot;自己一定要確認和測試。</li></ul><p></p><div><br /></div><div>先醬。Keep coding!</div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-6077884713786851392023-10-16T23:49:00.009+08:002023-10-18T22:18:23.754+08:00《高品質軟體文件》筆記:維持文件的正確性<p> 從書中摘出我特別有感的兩個段落,講的是關於保持文件的正確性(摘自第 1 章)。</p><span><a name='more'></a></span><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnW4U0Wx-Ie4Ip_IYR9dGf-ayXP7zzUTDaj8Fep4c8rekUiJxvcDxYfDk4pvqHJl3PHjyWkmVX5rr88JwfJpf5ba8WaF5bR410EfhP-6qO7Y47V4Jnd1kJGGY-Zcz-T11Cdx_tI92CLHkn5BkjZDxJn1ZYGlpOu57Zd5N9wqgDIY5QFNFpbWV7hUWAUFc/s359/living-doc-book.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="354" data-original-width="359" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnW4U0Wx-Ie4Ip_IYR9dGf-ayXP7zzUTDaj8Fep4c8rekUiJxvcDxYfDk4pvqHJl3PHjyWkmVX5rr88JwfJpf5ba8WaF5bR410EfhP-6qO7Y47V4Jnd1kJGGY-Zcz-T11Cdx_tI92CLHkn5BkjZDxJn1ZYGlpOu57Zd5N9wqgDIY5QFNFpbWV7hUWAUFc/s16000/living-doc-book.png" /></a></div><div><br /></div>書籍資料:<br /><p></p><ul style="text-align: left;"><li>中文書名:《高品質軟體文件》</li><li>原文書名:Living Documentation</li><li>作者:Cyrille Martraire</li><li>譯者:楊尊一</li><li>出版社:碁峰</li></ul><p></p>
<hr /><br /><div>
我也有買英文版,所以有時會中英交替著看。這裡我想從書中摘出我特別有感的兩個段落,講的是關於保持文件的正確性(摘自第 1 章)。最後是一點個人感想。</div><div><br /></div><div>以下先摘錄原文,然後是我修改後的中文翻譯。(註:不代表我修改後的翻譯一定更通順正確,只是我的一點個人偏執罷了。) </div><h1 style="text-align: left;">摘錄</h1><div>第一段,英文:</div><blockquote>Documentation that is not 100% accurate all the time cannot be trusted. As soon as you know documentation can be misleading from time to time, it loses its credibility. It might still be a bit useful, but it will take more time to find out what's right and what's wrong in it. And when it comes to creating documentation, it's hard to dedicate time to it when you know it won't be accurate for long; its lifespan is a big motivation killer.<br /></blockquote><p>第一段,中譯: </p><blockquote><p>沒有隨時保持 100% 正確的文件不可信。一旦你發現有時會被文件誤導,它便失去了可信度。它可能還有一點用處,但需要花更多時間去分辨哪些是對的、哪些是錯的。當你一開始寫文件的時候就知道無法讓它長期保持正確,便很難付出心力去寫;文件的壽命會大大削弱撰寫文件的動力。</p></blockquote><p>第二段,英文:</p><blockquote><p>But updating documentation is one of the most unappreciated tasks ever. It is not interesting and doesn’t seem rewarding. However, you can have nice documentation if you take it seriously and decide to tackle it with a well-chosen mechanism to enforce accuracy at all times. <br /> <br /><b>Therefore: You need to think about how you address the accuracy of your documentation.</b></p></blockquote><p>第二段,中譯:</p><blockquote><p>然而,更新文件是最不受重視的工作之一。它不有趣,似乎也不會有什麼回報。但如果你認真對待並決定採取適當機制來確保其正確性,便能寫出好文件。<br /><br /><b> 因此:你需要思考如何處理文件的正確性。</b></p></blockquote><h1 style="text-align: left;">感想 </h1><div><div>持續更新文件以保持其正確性,其中的付出和成效是很難被看見的,尤其在當下或短時間內。這是身為全職 technical writer 必須有的體認和覺悟。如果是 part-time 寫手或 freelancer,以我個人經驗來看,由於自己擁有較大的決定權,像是要不要寫某個主題、是否需要更新文件、何時更新等等,故相對比全職寫手更自由一些。</div><div><br /></div><div>能夠在不被看見的情況下還願意持續更新文件,就像程式設計師即使在沒有別人 review 的情況下依然堅持程式碼的品質,是需要一些信念來支撐的。</div><div><br /></div><div>也像是寫 blog,沒人按讚還是要寫,因為寫作本身往往就是最大的回報。</div><div><br /></div><div>Keep writing!</div></div><div><br /></div><div>本文已同步發於新站:<a href="https://huanlin.cc/blog/2023/10/16/living-doc-accuracy/" target="_blank">《高品質軟體文件》筆記:維持文件的正確性</a></div><div id="gtx-trans" style="left: -4px; position: absolute; top: 1604.59px;"><div class="gtx-trans-icon"></div></div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-84560092110102844632023-10-14T20:04:00.037+08:002023-10-15T00:08:27.076+08:00Azure OpenAI 入門 for .NET<p>只是玩一下 Azure OpenAI,從建立資源到撰寫程式來呼叫 OpenAI 的 Chat Completions API。</p><span><a name='more'></a></span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBtQpmal8Nfm5kocqBj_KidL55VmqRWrETc6h8hBLrhcr8GqiJuTxcKvT89_sObDE0K1Q-TIXb89s4WqITyQS3CNeAwdWBvNCnxu039wQ5YTyGWDH9nlRhkjcYtbrLIg0-73Mb-OAgNcE7ruXsNORTwEthz3SywcUXV7AiVrMb24WP8NqL93RxJJF88FI/s550/post-banner.png" style="margin-left: 1em; margin-right: 1em;"><br /><img border="0" data-original-height="258" data-original-width="550" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBtQpmal8Nfm5kocqBj_KidL55VmqRWrETc6h8hBLrhcr8GqiJuTxcKvT89_sObDE0K1Q-TIXb89s4WqITyQS3CNeAwdWBvNCnxu039wQ5YTyGWDH9nlRhkjcYtbrLIg0-73Mb-OAgNcE7ruXsNORTwEthz3SywcUXV7AiVrMb24WP8NqL93RxJJF88FI/w400-h188/post-banner.png" width="400" /></a></div><br /><p><b>前提</b>:要有 Azure 帳號(可<a href="https://azure.microsoft.com/en-us/free/" target="_blank">免費試用</a>)。</p><h1 style="text-align: left;">Step 1: Create an OpenAI Resource</h1><p>欲撰寫程式來使用 Azure OpenAI 服務,必須先在自己的 Azure 帳戶中建立 OpenAI 資源,並<a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?pivots=web-portal#deploy-a-model" target="_blank">設定一個 deployment</a> 來指定欲使用哪一個訓練模型(例如 gpt-35-turbo、gpt-4 等等)。</p><p><br /></p><p>首先,登入 <a href="https://portal.azure.com/" target="_blank">Azure 入口網站</a>,選擇建立資源(Create a resource)。然後在搜尋框中輸入 "openai",即可找到建立 OpenAI 資源的地方。(稍後有操作過程的影片連結)</p><p><br /></p><p>建立 OpenAI 資源時,頁面下方可能會看到如下圖的錯誤訊息:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHiu6eF5qVXNFVRRLfzgwiLFNYERzkNPYLK6gz8lSP8rFj_jrl3i3l1Zpqxe6YfJO5gpFbGHPP0kVsuiTm3_xirvhcMx4junRaTjgXV8xZLOKwJxIk3uaKBr-3DNDFVw51TN3-81M3YYNqFb0ShrPcfMcHjglsUybii9OF3U0Dom9LWW4YBBpRtzq0m1k/s1220/openai-need-apply.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="908" data-original-width="1220" height="476" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHiu6eF5qVXNFVRRLfzgwiLFNYERzkNPYLK6gz8lSP8rFj_jrl3i3l1Zpqxe6YfJO5gpFbGHPP0kVsuiTm3_xirvhcMx4junRaTjgXV8xZLOKwJxIk3uaKBr-3DNDFVw51TN3-81M3YYNqFb0ShrPcfMcHjglsUybii9OF3U0Dom9LWW4YBBpRtzq0m1k/w640-h476/openai-need-apply.png" width="640" /></a></div><br /><p>此訊息告訴我們:Azure OpenAI 目前必須透過一個申請程序,審核通過之後方能使用。此申請程序需要填寫一個線上表單,只要點擊訊息中的連結即可開啟那個線上申請表單。填寫表單時,有些欄位不可填錯,例如你的 Azure 訂閱 ID(subscription ID):</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhp71OwYQeJUYKKgZtMZXKOnFVspmawOSpuyDZ9FK6ZaGDwHQsti_VDXp6J5dEDV_0TgKpuCy6g_HQ4wzOz3DQ6nv9fWGs6dPijnB8WavFQ-DslBv7IuhyphenhyphenDYEIYhHtU5dbhHr_49pf8baW77cIMTisQGMgROE_wZYR0Y7kyRHdquFmAYSZ4ZcoT5ZMN4U0/s964/requst-form-subscription-id.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="478" data-original-width="964" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhp71OwYQeJUYKKgZtMZXKOnFVspmawOSpuyDZ9FK6ZaGDwHQsti_VDXp6J5dEDV_0TgKpuCy6g_HQ4wzOz3DQ6nv9fWGs6dPijnB8WavFQ-DslBv7IuhyphenhyphenDYEIYhHtU5dbhHr_49pf8baW77cIMTisQGMgROE_wZYR0Y7kyRHdquFmAYSZ4ZcoT5ZMN4U0/w640-h318/requst-form-subscription-id.png" width="640" /></a></div><div><br /></div>還有你任職公司的 email、地址、電話:<div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUR9tgqDIFixPJgtv6pfa74CBan5CV8Ftlqs_XQfQHnj62MNNCL1vf7gLLCPOtRChDqIdUGlEyrpOFQgSYUYu98pL0VG_NrwGQ-iIYHOp_1ngDs-BozX0o_Q1jotSPPuD4PDkl0Oct5wWxPhZFP99Llhb7JV4kP_sYvEc11oNSejdfw8owfFBWJUg7gIA/s934/requst-form-company-email.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="575" data-original-width="934" height="394" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUR9tgqDIFixPJgtv6pfa74CBan5CV8Ftlqs_XQfQHnj62MNNCL1vf7gLLCPOtRChDqIdUGlEyrpOFQgSYUYu98pL0VG_NrwGQ-iIYHOp_1ngDs-BozX0o_Q1jotSPPuD4PDkl0Oct5wWxPhZFP99Llhb7JV4kP_sYvEc11oNSejdfw8owfFBWJUg7gIA/w640-h394/requst-form-company-email.png" width="640" /></a></div><br /><div><p>如果沒有填寫正確的公司資料和 email,例如<span style="color: #cc0000;"><b>填寫 gmail、yahoo mail 等私人信箱,都無法通過審核</b></span>。</p><p><br /></p><p>如果你是 Microsoft MVP,在選擇組織類型的時候有對應的選項:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEin59yLFEuDidCUrjfpd0Ww3qi9upizUwxFCZicaCd8QrshzS7rbZxuk2huYn56BOrG8daKEyuA87tL_LbBFm7H7Ie66sLkpsx38KGaSuVfpjcHnMSDCRbVatY7RhyphenhyphenxfP15u8tRQEr1SMockgJzs7-xsznrLPjYB3Ih65Da-SfjRdJof31w99YpPsIeK9s/s937/requst-form-mvp.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="472" data-original-width="937" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEin59yLFEuDidCUrjfpd0Ww3qi9upizUwxFCZicaCd8QrshzS7rbZxuk2huYn56BOrG8daKEyuA87tL_LbBFm7H7Ie66sLkpsx38KGaSuVfpjcHnMSDCRbVatY7RhyphenhyphenxfP15u8tRQEr1SMockgJzs7-xsznrLPjYB3Ih65Da-SfjRdJof31w99YpPsIeK9s/w640-h322/requst-form-mvp.png" width="640" /></a></div><br /><p>另外還要勾選你需要使用 AI services 提供的哪些功能,例如 Language、Dall-E、Whisper 等等。我不知道怎樣填寫才一定能通過申請,我只是據實填寫。</p><p><br /></p><p>兩天後,我收到了核准通知的 email。然後我打開瀏覽器,登入 Azure 入口網站,前面圖中顯示的錯誤訊息便消失了,顯示我可以開始建立 OpenAI 資源。</p><p><br /></p><p>我錄製了一個影片放在 Youtube 上,可大致了解如何建立一個 OpenAI 資源,連結如下:</p><p><br /></p><p><b>Youtube 影片:<a href="https://www.youtube.com/watch?v=fvU4XLW7Zco" target="_blank">Create an Azure OpenAI Resource</a></b></p><h1 style="text-align: left;">Step 2: Call the API</h1><p>建立好 OpenAI 資源後,可以從 Azure 網站上找到三個關鍵資訊:</p><p><br /></p><p></p><ul style="text-align: left;"><li>OpenAI 的 API endpoint。</li><li>呼叫 API 時必須提供的 keys。</li><li>預先建立的 OpenAI deployment 的名稱。</li></ul><p></p><p><br /></p><p>下圖顯示的網頁可以找到你申請的 OpenAI 的 API endpoint 以及 keys。密鑰有兩支,任何一支都可以用。</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHHkrp9E9kWnVnTgE3xnLOnMr5ciQJvm3t_vVPUHdyEb8Hw9Yw7W2vE5ZeR50rkHh6dg8PjinJuqG1nHD2p1kFyqJKZOpPT0cBwIaqSbSdI_7fidDoWZD-gcgvl58wm3rYbfyjsE9LNYX47hY6W4ZTjJFZriJTryLh1MxFBciTjfXDeDskDtewzGXt100/s1082/openai-keys-endpoint.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="796" data-original-width="1082" height="470" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHHkrp9E9kWnVnTgE3xnLOnMr5ciQJvm3t_vVPUHdyEb8Hw9Yw7W2vE5ZeR50rkHh6dg8PjinJuqG1nHD2p1kFyqJKZOpPT0cBwIaqSbSdI_7fidDoWZD-gcgvl58wm3rYbfyjsE9LNYX47hY6W4ZTjJFZriJTryLh1MxFBciTjfXDeDskDtewzGXt100/w640-h470/openai-keys-endpoint.png" width="640" /></a></div><br /><p><br /></p><p>從上圖左方選單點擊<span face=""Noto Sans TC", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"" style="background-color: white; color: #212529; font-size: 16px;"> </span><code style="background-color: rgba(0, 0, 0, 0.05); border-radius: 0.375rem; box-sizing: border-box; color: #212529; font-family: consolas; font-size: 13.6px; margin: 0px; overflow-wrap: break-word; padding: 0.2em 0.4em; word-break: normal;">Model deployments</code><span face=""Noto Sans TC", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"" style="background-color: white; color: #212529; font-size: 16px;"> </span>即可得知先前建立好的 OpenAI deployment 名稱,如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRaC2AV2HH451-FsH5rAhIgvffAK1zAoKr8txEFOEzLsjzywgjhzjABGfC6jQ_5n2L55ifvlxGsXk5Xn9-YuW0SH8ZxOyZhmyi68cdyc9xHJxPaylBg1yPuYWczZUYf-Eo6LpO9Ms57fDtQ8wBrr03h0Qa845ebqqlSN5HYDlP3ZRdIvmk7XWXXqCpe4M/s1083/openai-deployments.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="650" data-original-width="1083" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRaC2AV2HH451-FsH5rAhIgvffAK1zAoKr8txEFOEzLsjzywgjhzjABGfC6jQ_5n2L55ifvlxGsXk5Xn9-YuW0SH8ZxOyZhmyi68cdyc9xHJxPaylBg1yPuYWczZUYf-Eo6LpO9Ms57fDtQ8wBrr03h0Qa845ebqqlSN5HYDlP3ZRdIvmk7XWXXqCpe4M/w640-h384/openai-deployments.png" width="640" /></a></div><br /><p><br /></p><p>有了上述資訊之後,便可以撰寫程式碼來呼叫 OpenAI 服務了。</p><p><br /></p><h2 style="text-align: left;">A REST Client Example</h2><p>如果你有使用 Visual Studio Code 和 <a href="https://marketplace.visualstudio.com/items?itemName=humao.rest-client">REST Client 擴充套件</a>,可以直接用以下範例來測試(必須把開頭三個變數替換成你自己的資源和設定):</p><p><br /></p>
<script src="https://gist.github.com/huanlin/6903e2139d805ee643a059a6a2a5840e.js"></script>
<br />
<p>請注意這裡的 endpoint 並不是先前網頁截圖中顯示的 <span face=""Noto Sans TC", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"" style="background-color: white; color: #212529; font-size: 16px;"> </span><code style="background-color: rgba(0, 0, 0, 0.05); border-radius: 0.375rem; box-sizing: border-box; color: #212529; font-family: consolas; font-size: 13.6px; margin: 0px; overflow-wrap: break-word; padding: 0.2em 0.4em; word-break: normal;">https://my-demo-ai-app.openai.azure.com/</code>(此 endpoint 會用於稍後的 c# 範例),而是後面有加上 deployment ID 和 Chat Completions API 路徑的 REST API endpoint:<br /><br /><span style="background-color: rgba(0, 0, 0, 0.05);"><span style="color: #212529; font-family: consolas;"><span style="font-size: 13.6px;">https://{{RESOURCE_NAME}}.openai.azure.com/openai/deployments/{{DEPLOYMENT_ID}}/chat/completions?api-version=2023-05-15/</span></span></span> </p><p><br /></p><p>有關 Chat Completions 的 REST API endpoint 格式與查詢參數,可參考官方文件:<a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions" target="_blank">Azure OpenAI Service REST API reference: Chat completions</a>。</p><p><br /></p><p>以上範例中,我在 POST request body 的 <code style="background-color: rgba(0, 0, 0, 0.05); border-radius: 0.375rem; box-sizing: border-box; color: #212529; font-family: consolas; font-size: 13.6px; margin: 0px; overflow-wrap: break-word; padding: 0.2em 0.4em; word-break: normal;">messages</code><span face=""Noto Sans TC", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"" style="background-color: white; color: #212529; font-size: 16px;"> </span>陣列中加入了兩條訊息:</p><p><br /></p><p></p><ul style="text-align: left;"><li>第一條訊息的角色是 <span style="background-color: rgba(0, 0, 0, 0.05); color: #212529; font-family: Consolas, "Liberation Mono", "Courier New", monospace; font-size: 13.6px;">system</span>,表示這是一則 system message,用來預先給 AI 助理一些指示,請她在接下來的回應中按照這個指示來產生回應內容。這裡我給她的指示為:「請用中文回答。」</li><li>第二條訊息的角色是 <span style="background-color: rgba(0, 0, 0, 0.05); color: #212529; font-family: Consolas, "Liberation Mono", "Courier New", monospace; font-size: 13.6px;">user</span>,表示這是一則 user message,亦即使用者的提問內容。OpenAI 服務會針對這個提問來產生回應。此範例是請 AI 助理說一則關於小狗的笑話。</li></ul><p></p><p><br /></p><p>執行結果如下:</p><p><br /></p><pre style="background-color: #f8f8f8; box-sizing: border-box; color: #212529; font-family: consolas; font-size: 0.9rem; margin-bottom: 0px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 1rem; tab-size: 4;" tabindex="0"><code class="language-json" data-lang="json" style="background-color: inherit; border: 0px; box-sizing: border-box; color: inherit; font-family: consolas; margin: 0px; overflow-wrap: break-word; padding: 0px; word-break: normal;"><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;">
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"><span style="box-sizing: border-box; color: black; font-weight: 700;">{</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"id"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #4e9a06;">"chatcmpl-89WTdFFwqgeIZG6WNJ0g3mOxhaOb3"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">,</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"object"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #4e9a06;">"chat.completion"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">,</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"created"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #0000cf; font-weight: 700;">1697280485</span><span style="box-sizing: border-box; color: black; font-weight: 700;">,</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"model"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #4e9a06;">"gpt-35-turbo"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">,</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"choices"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: black; font-weight: 700;">[</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: black; font-weight: 700;">{</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"index"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #0000cf; font-weight: 700;">0</span><span style="box-sizing: border-box; color: black; font-weight: 700;">,</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"finish_reason"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #4e9a06;">"stop"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">,</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"message"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: black; font-weight: 700;">{</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"role"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #4e9a06;">"assistant"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">,</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"content"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #4e9a06;">"狗为什么总是追自己的尾巴?因为它们担心自己跑得太快,把自己追上了!"</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: black; font-weight: 700;">}</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: black; font-weight: 700;">}</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: black; font-weight: 700;">],</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"usage"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: black; font-weight: 700;">{</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"prompt_tokens"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #0000cf; font-weight: 700;">25</span><span style="box-sizing: border-box; color: black; font-weight: 700;">,</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"completion_tokens"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #0000cf; font-weight: 700;">49</span><span style="box-sizing: border-box; color: black; font-weight: 700;">,</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: #204a87; font-weight: 700;">"total_tokens"</span><span style="box-sizing: border-box; color: black; font-weight: 700;">:</span> <span style="box-sizing: border-box; color: #0000cf; font-weight: 700;">74</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"> <span style="box-sizing: border-box; color: black; font-weight: 700;">}</span>
</span></span><span style="box-sizing: border-box; display: flex;"><span style="box-sizing: border-box;"><span style="box-sizing: border-box; color: black; font-weight: 700;">}</span></span></span></code></pre><p><br /></p><p>結果 AI 助理是以簡體中文來回應。在接下來的 C# 範例程式中,我會明確指示她以繁體中文來回應。</p><p><br /></p><h2 style="text-align: left;">A .NET Example</h2><p>以下範例是以 C# 撰寫的 .NET Console application。寫程式前,需在專案中加入套件:Azure.AI.OpenAI。此套件目前還是 beta 版,故加入套件時必須使用 <span style="background-color: rgba(0, 0, 0, 0.05); color: #212529; font-family: Consolas, "Liberation Mono", "Courier New", monospace; font-size: 13.6px;">prerelease</span> 選項。</p><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpryYVzg_jVfFtD84mFnhGIUoJ7SqaS2FxT2aJj7wjQ7TP0wEVSb8kWjatgL52Yka5c4-dc4tb_wTE-MoIPWq2fg0CHRevHjMzVnNYQE7sLo9tELLCLcKNWlMXQAZNfdHGud15PI-Shz6WsR4_2cd2CtAFIuCcgcXE4vLh2cgkGLmuP1znK-nfeNPMM4M/s1566/openai-package.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="856" data-original-width="1566" height="350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpryYVzg_jVfFtD84mFnhGIUoJ7SqaS2FxT2aJj7wjQ7TP0wEVSb8kWjatgL52Yka5c4-dc4tb_wTE-MoIPWq2fg0CHRevHjMzVnNYQE7sLo9tELLCLcKNWlMXQAZNfdHGud15PI-Shz6WsR4_2cd2CtAFIuCcgcXE4vLh2cgkGLmuP1znK-nfeNPMM4M/w640-h350/openai-package.png" width="640" /></a></div><br /><div><br /></div><div>程式碼如下:</div><div><br /></div>
<script src="https://gist.github.com/huanlin/7a563aa8535cbbf1527e066ef29947ca.js"></script>
<br />
<p>這次我在 system message 中明確指示 AI 助理要同時使用繁體中文和英文來回應(第 17 行程式碼),執行結果如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxTCmU3AFgYUo9H165Lo5JlmUIgyRaOco9DD76y2tOQjRNL6tEpc2a6V7Q0YSrfqodimOSVJApPN8Q9XTXldnCFHuLMCbJhjaArf7JwgSWu0C3zIAzF6HzDQ6GyyqhKX9kWmHE_fT9KKNqQPw5Kg935DzJzTziQuFSOFwIVeE_8kCeTfIzHRI8B6658Ec/s1001/openai-demo-result.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="414" data-original-width="1001" height="264" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxTCmU3AFgYUo9H165Lo5JlmUIgyRaOco9DD76y2tOQjRNL6tEpc2a6V7Q0YSrfqodimOSVJApPN8Q9XTXldnCFHuLMCbJhjaArf7JwgSWu0C3zIAzF6HzDQ6GyyqhKX9kWmHE_fT9KKNqQPw5Kg935DzJzTziQuFSOFwIVeE_8kCeTfIzHRI8B6658Ec/w640-h264/openai-demo-result.png" width="640" /></a></div><br /><p>如此便可確認 .NET 程式也能順利呼叫 OpenAI 服務。至於那個蝙蝠俠的梗(哏)好不好笑,就不是重點了。</p><p><br /></p><p>Keep coding!</p><p><br /></p><p>(此文章已同步發表於新站:<a href="https://huanlin.cc/blog/2023/10/14/get-started-with-azure-openai/" target="_blank">Get Started with Azure OpenAI</a>)</p><h1 style="text-align: left;">Reference</h1><p></p><ul style="text-align: left;"><li><a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?pivots=web-portal" target="_blank">Create and deploy an Azure OpenAI Service resource</a></li><li><a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/chatgpt?pivots=programming-language-chat-completions" target="_blank">Learn how to work with the GPT-35-Turbo and GPT-4 models</a></li><li><a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions" target="_blank">Azure OpenAI Service REST API reference: Chat completions</a></li></ul></div><br />Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-15071361516616738352023-10-09T18:19:00.012+08:002023-10-09T22:32:35.759+08:00C# Dev Kit 簡介<p>C# Dev Kit 簡介,包含兩個短片。</p><span><a name='more'></a></span><p><br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgikDFs9hA7qN_B_nww2YhTjmjv2KnVV80nv3CZx9s2jtzfTf3S1chLd-zTGPphTqZlLssT6AHlWIXFkK0-T4UUaYYM4w6TjJIYHOeiwM81tvBo9ZgfCmbtIsXm4M2JR7yAg9tD6SudKK7XhO3eQk90nz_oyqgH005fSLsIoUGUg_EuuxCiHhf_y2gQzWA/s768/csharp_dev_kit.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="377" data-original-width="768" height="314" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgikDFs9hA7qN_B_nww2YhTjmjv2KnVV80nv3CZx9s2jtzfTf3S1chLd-zTGPphTqZlLssT6AHlWIXFkK0-T4UUaYYM4w6TjJIYHOeiwM81tvBo9ZgfCmbtIsXm4M2JR7yAg9tD6SudKK7XhO3eQk90nz_oyqgH005fSLsIoUGUg_EuuxCiHhf_y2gQzWA/w640-h314/csharp_dev_kit.png" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">圖片來源:Microsoft</td></tr></tbody></table><p><br /></p><p><a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit" target="_blank">C# Dev Kit</a> 是一個 Visual Studio Code 擴充套件(extension),此套件是基於 <a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp" target="_blank">C# extension</a>,而且可以跟以下擴充套件一起使用:</p><p></p><ul style="text-align: left;"><li><a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscodeintellicode-csharp" target="_blank">C# Dev Kit IntelliCode</a>(會隨著 C# Dev Kit 一併安裝)</li><li><a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-maui" target="_blank">.NET MAUI extension</a></li><li><a href="https://marketplace.visualstudio.com/items?itemName=VisualStudioToolsForUnity.vstuc" target="_blank">Unity extension</a></li></ul><p></p><p>C# Dev Kit 並非開源專案,而且它只在某些條件可以免費使用:</p><p></p><ul style="text-align: left;"><li>可免費使用於個人專案、學術專案、和開源專案。</li><li>對於商業應用程式,若團隊成員不超過 5 人,可免費使用。若團隊有 6 位或更多開發人員,則必須有 Visual Studio Professional 或更高級的訂閱方案才算是合法使用。</li></ul><p></p><p>以下短片展示了如何在 Visual Studio Code 中 login 你的 Visual Studio 訂閱方案:</p><p><br /></p><p><b>Youtube 影片</b>:<a href="https://youtu.be/FPwCrmVFawU" target="_blank">C# Dev Kit - Login your Visual Studio subscription</a></p><p><br /></p><p>我也錄製了一個短片來展示如何在 Visual Studio Code 中創建和執行一個最簡單的 Hello World C# 專案:</p><p><br /></p><p><b>Youtube 影片</b>:<a href="https://youtu.be/MSYtwrLI-jk" target="_blank">C# Dev Kit - Create and run a HelloWorld project in Visual Studio Code</a></p><p><br /></p><h2 style="text-align: left;">功能</h2><div>目前 C# Dev Kit 已經具備一些基本功能,包括:</div><p></p><ul style="text-align: left;"><li>Solution Explorer</li><li>IntelliSense / Code completion</li><li>Go to Definition</li><li>Find All References</li><li>Find All Implementations</li><li>在 Unit Test tree 面板中<a href="Testing with C# Dev Kit" target="_blank">執行單元測試</a></li></ul><p></p><p><br /></p><p>也有一些尚未支援的功能,或者比較不方便的地方,例如:</p><ul style="text-align: left;"><li>沒有提供 NuGet Manager。</li><li>不支援 Symlink。</li><li>一定要用 Open Folder 的方式來載入 .sln 檔案,而不能直接開以 .sln 檔案。</li></ul><p></p><p><br /></p><h2 style="text-align: left;">結語</h2><p>隨著 C# Dev Kit 功能逐漸增加,應該可以期待往後在 Visual Studio Code 中撰寫與除錯 C# 應用程式會越來越方便。只是對於平日已經習慣在 Windows 平台上面使用 Visual Studio 2022 的人來說,C# Dev Kit 應該沒有太大的吸引力。</p><p><br /></p><p>我想,C# Dev Kit 的訴求會不會主要是吸引 Linux 和 Mac 這兩類族群的使用者?尤其微軟已經宣布 <a href="https://learn.microsoft.com/zh-tw/lifecycle/announcements/visual-studio-mac-end-of-servicing" target="_blank">Visual Studio for Mac 將於 2024 年 8 月 31 日淘汰</a>(其實我寫完這帖才知道),那麼 Mac 使用者將來要寫 .NET 程式的話,勢必得改用其他工具——如果不是 <a href="https://www.jetbrains.com/rider/" target="_blank">Rider</a>,大概就只剩下 C# Dev Kit + VS Code 可選了吧。</p>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-73690960965438528672023-08-13T17:30:00.016+08:002023-08-13T20:06:22.971+08:00發布新的筆記和文章網站<p>發布新的筆記與文章網站。</p><span><a name='more'></a></span><p><br /></p><h2 style="text-align: left;">TL;DR</h2><p><span face=""Noto Sans CJK TC", 微軟正黑體, sans-serif" style="background-color: white; color: #333333; font-size: 16px;">新網站: </span><a href="https://huanlin.cc/" style="background: 0px 0px rgb(255, 255, 255); border: 0px; color: #2a5a8e; font-family: "Noto Sans CJK TC", 微軟正黑體, sans-serif; font-size: 16px; margin: 0px; outline: 0px; padding: 0px; text-decoration-line: none; transition: all 0.3s ease 0s; vertical-align: baseline;">https://huanlin.cc</a> </p><p>或者用<span style="color: #333333;"> </span><a href="https://huanlin.cc/docs" style="background: 0px 0px rgb(255, 255, 255); border: 0px; color: #2a5a8e; font-family: "Noto Sans CJK TC", 微軟正黑體, sans-serif; font-size: 16px; margin: 0px; outline: 0px; padding: 0px; text-decoration-line: none; transition: all 0.3s ease 0s; vertical-align: baseline;">https://huanlin.cc/docs</a> 直接進入文章入口<span face=""Noto Sans CJK TC", 微軟正黑體, sans-serif" style="background-color: white; color: #333333; font-size: 16px;">。</span></p><p><span face=""Noto Sans CJK TC", 微軟正黑體, sans-serif" style="background-color: white; color: #333333; font-size: 16px;"><br /></span></p><h2 style="text-align: left;">說明</h2><p>為了替未來可能會用到的兩個技術文件需求預作準備,我註冊了一個新的網域 huanlin.cc,一方面作為練習和實驗,一方面也可能正式成為我的第二個筆記網站。這兩個需求是:</p><p></p><ol style="text-align: left;"><li>多版本 API 文件切換。</li><li>多國語系切換。</li></ol><p></p><p>基於以上需求,之前試過 <a href="https://docusaurus.io/" target="_blank">Docusaurus</a>,也試過 MkDocs 搭配 <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank">Material theme</a>,感覺都不錯。不過,最後嘗試了 Hugo + <a href="https://www.docsy.dev/" target="_blank">Docsy theme</a>,花了不少時間微調樣式,我想就不再換了。</p><p><br /></p><p>這個新搭建的筆記和文章網站的 URL 是 <a href="https://huanlin.cc">https://huanlin.cc</a>。如果要跳過 landing page,直接進入文件首頁的話,則是這個網址:<a href="https://huanlin.cc/docs">https://huanlin.cc/docs</a>。</p><p><br /></p><p>最近幾個星期以來,我為 huanin.cc 持續添加了文件和筆記,主要是從兩個地方搬過來:</p><p></p><ol style="text-align: left;"><li>我原先寫的 C# 筆記。</li><li>原本用 MkDocs + Material theme 搭建的「DevOps 小學堂」網站,我也把那個網站裡面的文章遷移到 huanlin.cc 了。(所以原先的「DevOps 小學堂」網站將會關閉)</li></ol><div><br /></div><div>另外,我也在 huanlin.cc 上面寫了一點<a href="http://huanlin.cc/en/docs/hugo-docsy/" target="_blank">英文筆記</a>,除了用來實際體驗任意切換語系的功能,也當作自己練習英文寫作的機會。</div><h3 style="text-align: left;">我的 Docsy 自訂樣式與擴充</h3><div>Docsy 雖然有許多現成的使用案例,包括 <a href="https://kubernetes.io/" target="_blank">kubernetes 官方文件</a>、<a href="https://www.kubeflow.org/" target="_blank">kubeflow</a> 等等,但預設的樣式風格實在不怎麼討喜,總覺得有點枯燥乏味。所以我四處尋找客製化的範例和解決方案,逐漸調整出我自己想要的網站風格。</div><div><br /></div><div>主要還是受到先前搭建 「DevOps 小學堂」網站的影響吧。我蠻喜歡 Material for MkDocs 的設計,所以在調整 huanlin.cc 網站樣式的時候,最初的目標是希望做出風格類似先前的「DevOps 小學堂」網站的樣子。目前已經大致完成,<a href="https://huanlin.cc/docs/" target="_blank">文件入口</a>的頁面看起來像這樣:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6ee32-V7pcLAxhDC_F6qeToqOCKRlTZsE6rqBd3uGHPgAvrMJRU5s3SYRUlPtXEWs8GW5yubSb7a3OsLSGBorubOiBCDS5gVWIMxeqQXVFf5Pd6xTX5KcR7IVOgurBjAyPZSKCY35lizhKNrjflQqLP5n6ug4rYYoqWMTyOo7hC1OzvuLTJTBdy04UzE/s1470/huanlin-cc-docs.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="827" data-original-width="1470" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6ee32-V7pcLAxhDC_F6qeToqOCKRlTZsE6rqBd3uGHPgAvrMJRU5s3SYRUlPtXEWs8GW5yubSb7a3OsLSGBorubOiBCDS5gVWIMxeqQXVFf5Pd6xTX5KcR7IVOgurBjAyPZSKCY35lizhKNrjflQqLP5n6ug4rYYoqWMTyOo7hC1OzvuLTJTBdy04UzE/w640-h360/huanlin-cc-docs.png" width="640" /></a></div><br /><div><br /></div><div>這個新網站也是架在 Github 平台,但我沒有開放 repository。如果你有興趣了解我對 Docsy 預設樣板做了哪些自訂調整和擴充,可以參考這篇筆記:<a href="https://huanlin.cc/blog/2023/08/12/%E7%B6%B2%E7%AB%99%E7%89%88%E9%9D%A2%E6%A8%A3%E5%BC%8F%E4%BF%AE%E6%94%B9%E8%A8%98%E9%8C%84/" target="_blank">網站版面樣式修改記錄</a>。裡面有提及一個公開的 GitHub repo,我把所有客製化的檔案都擺進去了,可直接取用。</div><div><br /></div><div>另外,我還有一些 Hugo 和 Docsy 筆記是用英文撰寫,也是放在新網站:<a href="https://huanlin.cc/en/docs/hugo-docsy/" target="_blank">Hugo & Docsy</a>。</div><h2 style="text-align: left;">心得感想</h2><div>對於像我這樣一個不熟悉前端技術的人來說,調整網站風格直到我滿意的樣子,意外地花了我好多時間。常常為了調整一個小地方(例如 padding 或 margin 增加或縮小幾點),不知不覺已經過了幾十分鐘(其中一些時間花在找出 Docsy 的「機關」)。</div><div><br /></div><div>幾年前,我就曾經嘗試搬遷部落格,主要原因是 Google Blogger 平台沒辦法用 Markdown 撰寫。後來因為某些原因(我忘了 XD)而放棄,又回到這個部落格繼續發布筆記,但其實後來已經逐漸轉變成這樣的流程:先寫 Markdown,然後把生成的 HTML 貼到這個部落格來發布。</div><div><br /></div><div>這次花了更多時間來建立新網站,希望就此大致底定,不再對網站樣式東改西改了。(不然正事都甭做啦!)</div><div><br /></div><div>Keep learning!</div><div><br /></div><p></p><div id="gtx-trans" style="left: 602px; position: absolute; top: 1531.26px;"><div class="gtx-trans-icon"></div></div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-68761121857136731392023-07-16T02:18:00.005+08:002023-07-16T02:26:51.008+08:00GitOps 簡介<p><strong style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">摘要:</strong><span color="rgba(0, 0, 0, 0.87)" face=""Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif" style="background-color: white; font-size: 16px;">介紹 GitOps 的基礎觀念,以了解其內涵(如 IaC、合併請求),以及為什麼要使用它。</span></p><p><span></span></p><a name='more'></a><span color="rgba(0, 0, 0, 0.87)" face=""Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif" style="background-color: white; font-size: 16px;"><br />(<span face=""Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif" style="background: 0px 0px; border: 0px; color: #333333; margin: 0px; outline: 0px; padding: 0px; transition: all 0.3s ease 0s; vertical-align: baseline;">本文轉載自 </span><a href="https://huanlin.github.io/devops-notes/" style="background: 0px 0px; border: 0px; color: #2a5a8e; font-family: "Noto Sans CJK TC", 微軟正黑體, sans-serif; margin: 0px; outline: 0px; padding: 0px; text-decoration-line: none; transition: all 0.3s ease 0s; vertical-align: baseline;" target="_blank">DevOps 小學堂</a><span face=""Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif" style="background: 0px 0px; border: 0px; color: #333333; margin: 0px; outline: 0px; padding: 0px; transition: all 0.3s ease 0s; vertical-align: baseline;">,也就是我用來存放 DevOps 學習筆記的地方。)</span></span><div><span color="rgba(0, 0, 0, 0.87)" face=""Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif" style="background-color: white; font-size: 16px;"><span face="Noto Sans TC, -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif" style="color: #333333;"><br /></span></span><p></p><p><span color="rgba(0, 0, 0, 0.87)" face=""Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif" style="background-color: white; font-size: 16px;">先備知識:熟悉 Git 版本控制,且知道何謂 CI/CD 管線(pipeline)。</span></p><h2 id="what" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">What</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">GitOps 是什麼?簡單來說,就是以 <mark style="-webkit-box-decoration-break: clone; box-sizing: inherit; word-break: break-word;">正確的方式</mark> 來實現 IaC(Infrastructure as Code;基礎設施即代碼),即以程式碼(組態檔、指令腳本等等)來定義基礎設施。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">這裡的基礎設施,指的是運行應用程式或日常操作所需要的作業環境,包括作業系統、資料庫、網路、容器、CD/CD 工具……等等,都可能是基礎設施涵蓋的範圍。</p><h2 id="why" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">Why</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">相較於傳統的人工操作方式來建立基礎設施,IaC 強調以宣告的方式來達成相同目的。所謂宣告的方式,即是以 JSON 或 YAML 這類純文字檔案來描述目標作業環境的基礎設施——只需描述我們想要達成什麼狀態和結果,而實際的布建與設定等相關工作則全部由工具代勞,其最大優點是可以重複執行、重複使用,而且容易自動化。故 IaC 概念逐漸普及,演變成幾乎與電腦設備相關的東西都可以用程式碼來定義,例如網路組態、安全政策、系統組態等等,對象已經不僅只是 infrastructure 了,而是 Everything as Code,故後來也有 X as Code 的說法。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><div class="admonition note" style="border-radius: 0.2rem; border: 0.05rem solid rgb(68, 138, 255); box-shadow: var(--md-shadow-z1); box-sizing: inherit; break-inside: avoid; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 0.9rem; margin: 1.5625em 0px; padding: 0px 0.6rem;"><p class="admonition-title" style="background-color: rgba(68, 138, 255, 0.1); border-bottom: none; border-image: initial; border-left: 0.2rem none; border-right: none; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; border-top: none; box-sizing: border-box; font-weight: 700; margin: 0px -0.6rem; padding: 0.4rem 0.6rem 0.4rem 2rem; position: relative;">Note</p><p style="box-sizing: border-box;">各家雲端廠商有各自的 IaC 工具來協助部署應用程式到雲端平台或管理雲端上面的伺服器,例如 Amazon CloudFormation,它能夠讓我們以宣告的方式來布建(provision)整個 AWS 應用程式架構。又如微軟的 Azure Resource Manager 和 Google 的 Cloud Deployment Manager,也都是同類型的工具。</p><p style="box-sizing: border-box; margin-bottom: 0.6rem;"><br />雖然各家雲端平台都有提供類似工具讓我們以宣告的方式來定義和管理伺服器,但各家工具也只能用在自家平台上。比如說,Amazon 的工具並沒有辦法用來管理 Azure 平台上的伺服器,反之亦然。如果有一種工具能夠支援各家雲端平台,自然提供了更多方便與彈性,這也是 Terraform 與 Ansible 等工具受到普遍歡迎的原因之一。</p></div><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">舉例來說,原本以手動方式在 AWS 平台上建立伺服器、設定網路,以及建立 Kubernetes 叢集,現在改成編寫 Terraform 組態檔、Ansible 腳本、Kubernetes manifest 檔案,以及其他用來描述系統環境設定的 YAML 檔案。換言之,原先的人工作業幾乎都可以用文字檔來定義,然後交給工具執行。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">於是,我們可能會在自己的電腦(或者某一台開發專用的主機)上面編寫與測試這些組態檔與腳本,並且將這些檔案保存至一個 Git 儲存庫中,以便多人共同開發與管理版本。然而,這個多人共同開發的流程有可能不夠嚴謹,例如沒有 Pull Request 或 Merge Request(合併請求)機制,也沒有 review 程序,於是任何人修改檔案之後,只要 commit 然後 push,就可以直接把程式碼送進主分支;又或者沒有自動測試機制,故每當程式碼有改動,有時遺漏了測試工作,導致部署到正式作業環境之後出現一堆狀況。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><div class="admonition note" style="border-radius: 0.2rem; border: 0.05rem solid rgb(68, 138, 255); box-shadow: var(--md-shadow-z1); box-sizing: inherit; break-inside: avoid; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 0.9rem; margin: 1.5625em 0px; padding: 0px 0.6rem;"><p class="admonition-title" style="background-color: rgba(68, 138, 255, 0.1); border-bottom: none; border-image: initial; border-left: 0.2rem none; border-right: none; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; border-top: none; box-sizing: border-box; font-weight: 700; margin: 0px -0.6rem; padding: 0.4rem 0.6rem 0.4rem 2rem; position: relative;">PR 與 MR</p><p style="box-sizing: border-box; margin-bottom: 0.6rem;">PR(Pull Request)和 MR(Merge Request)指的是同一件事:合併請求。GitHub 平台稱之為 PR,而 GitLab 稱為 MR。基本的做法是,開發人員在自己的 git 分支修改程式,等到修改完成,並不是直接合併到主分支,而是提出一個合併請求,藉此通知其他團隊成員:「我這邊有程式碼可以準備合併到主分支了,大家可以幫我 review 一下嗎?」等大家看過之後沒問題,才由某人核准這次的合併請求,然後程式碼才會合併到主分支。</p></div><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">除了上述問題,各人在自己的開發機器上執行腳本來變更遠端伺服器,即表示每個有權限部署的人都可以直接存取並修改遠端伺服器的作業環境。萬一出狀況,可能不容易追查是誰造成的,以及當時做了哪些變動。底下是手動部署的示意圖:</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/argo-cd/images/manually-push-changes.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/argo-cd/images/manually-push-changes.png" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">上述情形雖然不見得會發生在每個團隊,但顯然只有 IaC 是不夠的,我們還需要搭配一套嚴謹的部署程序來確保每一項工作都按照標準程序來執行。這便是 GitOps 所要解決的問題。</p><h2 id="how" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">How</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">基本上,GitOps 就是把開發與管理應用程式原始碼的那一套程序照搬到 Infrastructure as Code。也就是說,我們會有一個專門用來保存基礎設施相關腳本與組態檔的 Git 儲存庫(所以叫做 GitOps),而且有一套 CI/CD 流程來自動執行測試、整合、與部署等工作。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">在 GitOps 流程中,部署是自動執行的。也就是說,一旦基礎設施的腳本通過持續整合程序,進入持續部署階段時,就會由工具自動將那些腳本套用至遠端機器。部署的做法有兩種:Push(推送)和 Pull(拉取)。</p><h3 id="push-vs-pull-mode" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.25em; letter-spacing: -0.01em; line-height: 1.5; margin: 1.6em 0px 0.8em;">Push vs. Pull mode</h3><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">推送模式就是以往我們熟悉的那種部署應用程式的方式,透過 Jenkins、GitLab 等工具來執行一些命令,把應用程式部署到另一台機器上。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/argo-cd/images/push-model.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/argo-cd/images/push-model.png" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">拉取模式則是在目標環境中安裝一個代理程式(agent),並由該 agent 主動去我們的 Git 儲存庫拉回檔案,如下圖所示。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/argo-cd/images/pull-mode.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/argo-cd/images/pull-mode.png" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">那麼,agent 怎麼知道何時該去儲存庫拉回檔案,然後更新至目標環境呢?</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">在拉取模式中,agent 會持續比對儲存庫中的組態檔所定義的狀態以及目標環境當前的狀態,當兩邊的狀態有差異,agent 就會自動執行拉取和部署工作,以便讓目標環境的狀態符合組態檔所描述的狀態。萬一某次 commit 至儲存庫的組態檔有錯誤,導致目標作業環境中的應用程式故障,可能只要透過 git revert 命令將檔案回復至先前的版本就能讓伺服器恢復正常運作。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">採用拉取模式的 CD(持續部署)工具當中,目前比較知名的是 Flux CD 和 Argo CD。它們都能夠運行在 Kubernetes 叢集中,並透過自動比對機制來確保目標作業環境的狀態符合儲存庫中定義的狀態。</p><h2 id="_1" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">結語</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">大致了解 GitOpt 的 what、why、與 how 之後,這裡做個簡單總結:理想情況是所有基礎設施和應用程式都能透過各種工具的組態檔與腳本來定義(即 IaC),而這些檔案則全部集中放在 Git 儲存庫,成為<strong style="box-sizing: inherit;">單一資訊來源</strong>(single source of truth),然後搭配合併請求(PR 或 MR)、code review 等實務做法以及 CD/CD 工具來實現自動部署。剛才這一長串的文字描述可濃縮成一個好記的公式(摘自免費電子書 <a href="https://page.gitlab.com/resources-ebook-beginner-guide-gitops.html" rel="noopener" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;" target="_blank">A Beginner's Guide to GitOps</a>):</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;"><mark style="-webkit-box-decoration-break: clone; box-sizing: inherit; word-break: break-word;"><strong style="box-sizing: inherit;">GitOps = IaC + MRs + CI/CD</strong></mark></p><h2 id="_2" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">參考資料</h2><ul style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;"><a href="https://page.gitlab.com/resources-ebook-beginner-guide-gitops.html" rel="noopener" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;" target="_blank">A Beginner's Guide to GitOps</a> (由 GitLab 提供免費下載)</li><li style="box-sizing: inherit; margin-bottom: 0px; margin-left: 1.25em;"><a href="https://www.youtube.com/watch?v=f5EpcWp0THw" rel="noopener" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;" target="_blank">What is GitOps, How GitOps works and Why it's so useful</a></li></ul><h2 id="_3" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">下一步</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 16px;">認識 <a href="https://huanlin.github.io/devops-notes/argo-cd/argo-cd-overview/" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">Argo CD</a>。</p></div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-44446849685911010952023-07-13T21:45:00.005+08:002023-07-13T22:05:30.286+08:00快速了解 MkDocs + Material 能幫你建立怎樣的文件網站<p> 一分鐘快速了解【MkDocs + Material 主題】能幫你建立怎樣的文件網站。</p><span><a name='more'></a></span><p><br /></p><p>以我的 DevOps 小學堂網站為例,錄製了一個短片,可以快速了解 MkDocs + Material 主題再搭配一些 plugins 能為你的文件網站提供哪些效果。</p><p><br /></p><p>影片如下,或者可以<a href="https://www.youtube.com/watch?v=kI5udBdaP5s" target="_blank">到我的 Youtube 頻道觀看</a>。</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="332" src="https://www.youtube.com/embed/kI5udBdaP5s" width="485" youtube-src-id="kI5udBdaP5s"></iframe></div><br /><h3 style="text-align: left;">一點心得筆記:</h3><p>還有一樣功能,我覺得也很直觀、好用:提供使用者切換文件的語言版本。需要的 plugin 是 <a href="https://github.com/ultrabug/mkdocs-static-i18n" target="_blank">mkdocs-static-i18n</a>。</p><p><br /></p><p>這裡說的不只是網站操作介面支援 i18n,而是能任意切換你寫的文件語言。當然前提是你得針對不同語言撰寫不同版本的文件。由於我的網站並未打算提供雙語版本,所以在影片中沒有展示這項功能,但我試過真的不錯。唯一可惜是卡在此 plugin 依賴的另一個元件 <a href="https://pypi.org/project/lunr/" target="_blank">Lunr</a> 尚未支援中文。只能等等看,或者自己改原始碼了。</p><p><br /></p><p>另外,我還發現一些瓶頸:</p><p></p><ul style="text-align: left;"><li>中文搜尋。如前所述,問題一樣卡在外部依賴的元件 <a href="https://lunr.readthedocs.io/en/latest/languages.html" target="_blank">Lunr 不支援中文搜尋</a>。</li><li>多版本的 API 文件,而且要讓使用者自由切換 API 版本。我嘗試的 plugin 是 <a href="https://squidfunk.github.io/mkdocs-material/setup/setting-up-versioning/" target="_blank">MkDocs Material 官方文件</a>裡面建議的 mike,但我並沒有成功,問題卡在 GitLab CI/CD 這關。後來爬文後發現,mike 的作者在某一個討論串裡面說他並不熟 GitLab,所以他的文件裡面也只展示了如何用在 GitHub 的 CI/CD pipeline。</li></ul><p></p><p><br /></p><p>要做到讓使用者任意切換版本的 API 文件並不容易,關於這個部分,我也許會找時間評估 Docusaurus 和 Hugo。</p><p><br /></p><p>~先醬~</p>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-79276258825647992023-07-11T22:51:00.007+08:002023-07-12T22:43:36.245+08:00Terraform 簡介<p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><strong style="box-sizing: inherit;">摘要:</strong>簡單介紹 Terraform 在布建基礎設施方面的功能、Terraform 的運作方式、組態檔,以及幾個重要的 Terraform 命令。</p><span><a name='more'></a></span><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><span face=""Noto Sans CJK TC", 微軟正黑體, sans-serif" style="color: #333333; font-size: 16px;">本文轉載自 </span><a href="https://huanlin.github.io/devops-notes/" style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: 0px 0px; background-repeat: initial; background-size: initial; border: 0px; color: #2a5a8e; font-family: "Noto Sans CJK TC", 微軟正黑體, sans-serif; font-size: 16px; margin: 0px; outline: 0px; padding: 0px; text-decoration-line: none; transition: all 0.3s ease 0s; vertical-align: baseline;" target="_blank">DevOps 小學堂</a><span face=""Noto Sans CJK TC", 微軟正黑體, sans-serif" style="color: #333333; font-size: 16px;">,也就是我用來存放 DevOps 學習筆記的地方。</span></p><h2 id="terraform_1" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">什麼是 Terraform?</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">Terraform 是:</p><ul style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">開源工具。</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">可自動布建基礎設施、軟體平台,以及運行於該平台之上的服務。</li><li style="box-sizing: inherit; margin-bottom: 0px; margin-left: 1.25em;">可實現「基礎設施即代碼」(Infrastructure as Code,簡稱 IaC),亦即以宣告的方式(declarative)來描述我們想要做什麼,以及希望達成的結果。我們不用費心描述「怎麼做」,Terraform 會想辦法按照指示來完成工作。</li></ul><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">以上簡單的描述文字當中,特別需要留意的關鍵字是:自動、布建基礎設施、和宣告的方式。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">自動的意思很直觀,優點是省時省力,並減少因為人工作業而容易犯的錯誤。那麼,「布建基礎設施」(provisioning infrastructure)指的又是什麼呢?</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><div class="admonition note" style="border-radius: 0.2rem; border: 0.05rem solid rgb(68, 138, 255); box-shadow: var(--md-shadow-z1); box-sizing: inherit; break-inside: avoid; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 0.7rem; margin: 1.5625em 0px; padding: 0px 0.6rem;"><p class="admonition-title" style="background-color: rgba(68, 138, 255, 0.1); border-bottom: none; border-image: initial; border-left: 0.2rem none; border-right: none; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; border-top: none; box-sizing: border-box; font-weight: 700; margin: 0px -0.6rem; padding: 0.4rem 0.6rem 0.4rem 2rem; position: relative;">Note</p><p style="box-sizing: border-box; margin-bottom: 0.6rem;">平日討論碰到需要講 infrastructure 的時候,口語上經常會簡稱「infra」。</p></div><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">舉例來說,你正在開發一個新的軟體專案,其中包含一些微服務,於是你打算準備一個作業環境來運行這些微服務。這些準備作業環境的工作,就是所謂的「布建基礎設施」。比如說,你可能會需要建立幾台虛擬機器,將你的微服務打包成幾個 Docker 容器,另外還需要部署一個資料庫容器。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">假設你打算在 AWS 上面建立這些基礎設施,那麼接下來你可能需要在 AWS 平台上進行下列操作:</p><ul style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">建立虛擬私人網路(virtual private network;VPC)。</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">建立 EC2 伺服器(虛擬機器)。</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">在每一台虛擬機器上安裝 Docker 以及運行你的 app 所需要的相關軟體。</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">建立 AWS 使用者帳號並設定權限。</li><li style="box-sizing: inherit; margin-bottom: 0px; margin-left: 1.25em;">設定防火牆。</li></ul><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">一旦基礎設施準備就緒,便可將你的 app 或 Docker 容器部署到那個基礎設施上面,令它們開始運行。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">剛才描述的過程可區分為兩大塊:一個是準備基礎設施的工作,另一個是部署應用程式的工作。在公司裡面,可能有兩個團隊(或兩位工程師)各自負責其中一塊。所以,一種可能的情況是由 DevOps 團隊來負責基礎設施的部分,然後由開發人員來將應用程式部署到預先準備好的基礎設施平台上。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">那麼,Terraform 在其中佔據哪個位置、擔任什麼角色呢?</p><h3 id="_1" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.25em; letter-spacing: -0.01em; line-height: 1.5; margin: 1.6em 0px 0.8em;">布建基礎設施</h3><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">Terraform 的能力主要是用在第一塊,也就是布建基礎設施的部分。剛才舉例提到的建立 VPC、EC2 伺服器、安裝 Docker 與相關軟體、設定使用者帳號與權限、設定防火牆等工作,Terraform 都能派上用場。這些工作當然也可以由工程師手動進行,但如果交給 Terraform,不僅更輕鬆,而且更容易確保各項工作都按照我們期望的順序逐一完成。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><div class="admonition note" style="border-radius: 0.2rem; border: 0.05rem solid rgb(68, 138, 255); box-shadow: var(--md-shadow-z1); box-sizing: inherit; break-inside: avoid; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 0.7rem; margin: 1.5625em 0px; padding: 0px 0.6rem;"><p class="admonition-title" style="background-color: rgba(68, 138, 255, 0.1); border-bottom: none; border-image: initial; border-left: 0.2rem none; border-right: none; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; border-top: none; box-sizing: border-box; font-weight: 700; margin: 0px -0.6rem; padding: 0.4rem 0.6rem 0.4rem 2rem; position: relative;">Ansible vs. Terraform</p><p style="box-sizing: border-box;">有個常見的問題是:Ansible 和 Terraform 二者功能似乎差不多,該用哪個?</p><p style="box-sizing: border-box;">二者皆可實現 Infrastructure as Code,功能也有若干重疊,故常令人不知該如何選擇。嚴格來說,Ansible 的主要能力是組態配置(configuration),它可以配置基礎設施的組態,也可以用來安裝軟體和部署應用程式。Terraform 的強項則是布建與管理基礎設施,至於部署應用程式方面的工作,亦可能搭配其他工具來達成。從年齡來看,Ansible 相對成熟,而 Terraform 是後起之秀,在調度(orchestration)方面有更多進階的功能。</p><p style="box-sizing: border-box; margin-bottom: 0.6rem;">實務上,DevOps 團隊經常是二者並用——依實際需要解決的問題來選擇最適合的工具。</p></div><h3 id="_2" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.25em; letter-spacing: -0.01em; line-height: 1.5; margin: 1.6em 0px 0.8em;">調整基礎設施</h3><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">基礎設施一旦建立完成,並非永久不變。以前面的例子來說,假設我們已經用 Terraform 在 AWS 上面建立好基礎設施,應用程式也已經部署到環境中順利運行。然後,有一天發現需要再加五台虛擬機器來部署更多新開發的微服務,或者需要修改一些安全設定、刪除某些資源,諸如此類的,這些工作都是針對既有的基礎設施來進行調整,而且未來還會持續發生。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">由於 Terraform 能夠讓我們以文字描述的方式來定義該做哪些事,故只要搭配持續整合工具來對既有的基礎設施進行必要的調整與改善,便可輕鬆達成任務。</p><h3 id="_3" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.25em; letter-spacing: -0.01em; line-height: 1.5; margin: 1.6em 0px 0.8em;">複製基礎設施</h3><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">Terraform 的另一個用途是複製既有的基礎設施。比如說,當產品準備好發布到正式環境時,我們可以透過 Terraform 來自動將軟體開發時期所佈建的基礎設施複製一份到正式環境。如果需要測試環境或預備環境(staging environment),也是同樣的作法。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">大致了解 Terraform 的功能與用途之後,接著說明其運作方式。</p><h2 id="terraform_2" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">Terraform 的運作方式</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">前面提過 Terraform 能夠在 AWS 上面建立 VPC、建立 EC2 伺服器、設定網路等等。那麼,它是如何連接 AWS 或者其他雲端平台的服務呢?</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">這些工作需要倚賴 Terraform 的兩個主要元件:Core,以及特定技術的提供者(providers)。</p><h3 id="core" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.25em; letter-spacing: -0.01em; line-height: 1.5; margin: 1.6em 0px 0.8em;">Core</h3><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">Core 使用兩項資訊來決定它要執行哪些工作:</p><ul style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">Terraform 組態檔:描述該做什麼、達成什麼狀態。</li><li style="box-sizing: inherit; margin-bottom: 0px; margin-left: 1.25em;">State:基礎設施目前的狀態。</li></ul><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">Core 會根據組態檔描述的需求以及基礎設施當前的狀態來決定該做哪些事情。也就是說,持續比對需求與現狀,一旦發現二者有差異,便可執行必要的工作來讓基礎設施達到組態檔所描述的狀態。</p><h3 id="_4" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.25em; letter-spacing: -0.01em; line-height: 1.5; margin: 1.6em 0px 0.8em;">特定技術的提供者</h3><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">Terraform 的另一個主要元件是提供者(providers),例如 AWS、Azure、Google Cloud Platform,或其他 IaaS 平台。簡單來說,提供者就像是連接各種技術或服務平台的管道,而藉由該管道,不同的技術平台之間便可相互連接、存取對方提供的服務。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><div class="admonition note" style="border-radius: 0.2rem; border: 0.05rem solid rgb(68, 138, 255); box-shadow: var(--md-shadow-z1); box-sizing: inherit; break-inside: avoid; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 0.7rem; margin: 1.5625em 0px; padding: 0px 0.6rem;"><p class="admonition-title" style="background-color: rgba(68, 138, 255, 0.1); border-bottom: none; border-image: initial; border-left: 0.2rem none; border-right: none; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; border-top: none; box-sizing: border-box; font-weight: 700; margin: 0px -0.6rem; padding: 0.4rem 0.6rem 0.4rem 2rem; position: relative;">IaaS</p><p style="box-sizing: border-box; margin-bottom: 0.6rem;">IaaS 是 Infrastructure as a Service 的縮寫,中文是「基礎設施即服務」。</p></div><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">舉例來說,透過 AWS 提供者,你就能存取數百種 AWS 資源,像是 EC2 伺服器、AWS 使用者等等。透過 Kubernetes 提供者,則能夠存取 Kubernetes 的資源,如 Services、命名空間等等。剛才提到的 Core 元件就需要倚賴這些提供者,以串接 API 的方式來執行它的任務。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">Terraform 的提供者數量眾多,下圖僅只是從官方網站的<a href="https://registry.terraform.io/browse/providers" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">提供者清單</a>擷取其中一小部分:</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/terraform/images/terraform-providers.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/terraform/images/terraform-providers.png" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><h2 id="terraform_3" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">Terraform 組態檔</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">前面提過 Terraform 是以組態檔來描述我們期望基礎設施應該符合的狀態,現在就以一個簡單的範例來看一下 Terraform 組態檔大概長什麼樣子(檔案名稱是 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">main.tf</code>):</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwpR8K1PlT6Bap94vFbLyTt8OFcWeRrdIVofuc9_dc9PE3e0dx4Cc6aD-aAy4CoYbsppgjUna9qT02sSymT9Tv5HqeQNH0rz0SVqtdCQtbjFxCeIgL7XT99oo93CFqYNMhvveCeg8ci6_f6-_BWpMWxIdAksEp4ptUIvwOSwW5rA0czY0bycMICOa7e2k/s469/2023-07-11_22-48-34.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="469" data-original-width="464" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwpR8K1PlT6Bap94vFbLyTt8OFcWeRrdIVofuc9_dc9PE3e0dx4Cc6aD-aAy4CoYbsppgjUna9qT02sSymT9Tv5HqeQNH0rz0SVqtdCQtbjFxCeIgL7XT99oo93CFqYNMhvveCeg8ci6_f6-_BWpMWxIdAksEp4ptUIvwOSwW5rA0czY0bycMICOa7e2k/s16000/2023-07-11_22-48-34.png" /></a></div><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><div class="language-hcl highlight" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">
<pre id="__code_1" style="box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; display: flow-root; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-variant-ligatures: none; line-height: 1.4; margin-bottom: 1em; margin-top: 1em; position: relative;"><span style="box-sizing: inherit;"></span>
</pre></div><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">Terraform 組態檔是以 <a href="https://developer.hashicorp.com/terraform/language" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">HCL 語法</a>語法來撰寫,副檔名是 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">.tf</code>,例如 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">main.tf</code>。前述範例當中有三個區塊(由一對大括號 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">{...}</code> 來辨別):</p><ul style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">第一個區塊的 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">required_providers</code> 宣告了我們的專案所需要用到的提供者。此範例只使用一個提供者:<code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">aws</code>,<code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">source</code> 屬性告訴 Terraform 去哪裡下載這個提供者的程式碼,<code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">version</code> 屬性則指定版本號碼。</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">第二個區塊宣告此專案要使用 AWS 雲端服務。</li><li style="box-sizing: inherit; margin-bottom: 0px; margin-left: 1.25em;">第三個區塊則表示需要建立一個 VPC,而該 VPC 資源的類型是 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">aws_vpc</code>,名稱是 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">example</code>。</li></ul><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">由以上範例可以看得出來,HCL 語法相當簡單,直觀,基本上就是在描述資源的類型、名稱,然後設定其屬性。你不會在組態檔案中撰寫類似「增加 2 台 VM」、「刪除某個 AWS 使用者的權限」這種命令式語法。就如前面提過的,Terraform 的組態配置是採用宣告的方式——我們只需定義 what,不用去指示 how。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">值得一提的是,範例中的第一個區塊,也就是宣告 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">required_providers</code> 的部分,如果是 Terraform 官方註冊的提供者(例如 aws),即使沒有寫這個區塊也能運作,但一般還是建議明白宣告專案會用到哪些提供者。如果用到的提供者數量較多,亦可將這塊宣告抽離出去,單獨放在一個名為 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">providers.tf</code> 的檔案裡。至於非官方的提供者,就一定要明白宣告,否則 Terraform 會不知道如何獲取提供者相關資訊。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">Terraform 網站的<a href="https://registry.terraform.io/browse/providers" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">提供者清單頁面</a>中,有提供選項與圖示可以快速尋找與辨認哪些是官方的提供者、哪些是由合作夥伴或第三方社群提供。如下圖:</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/terraform/images/terraform-providers-filters.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/terraform/images/terraform-providers-filters.png" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">初次撰寫 Terraform 組態檔案時,可參考官方網站上提供的<a href="https://developer.hashicorp.com/terraform/tutorials" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">教學文件</a>,並利用文件中提供的現成範例來學習組態檔的語法。以下動畫展示了如何在官網上面查找 AWS Provider 的用法與範例。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/terraform/images/terraform-registry-providers.gif" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/terraform/images/terraform-registry-providers.gif" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><h2 id="terraform_4" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">Terraform 的常用命令</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">大致了解 Terraform 的運作方式與組態檔的語法之後,接著來看幾個常用的基本命令。</p><ul style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;"><strong style="box-sizing: inherit;">refersh</strong>:令 Terraform 查詢目前的狀態。此時可能會呼叫提供者(例如 AWS)的 API 來取得當前狀態。</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;"><strong style="box-sizing: inherit;">plan</strong>:令 Terraform 的 Core 元件根據組態檔與當前狀態的差異來建立工作計畫。這個步驟就像是預覽稍後需要執行哪些工作,並沒有真正去執行工作。</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;"><strong style="box-sizing: inherit;">apply</strong>:執行上一個步驟所建立的工作計畫。當你下達此命令時,Terraform 會依序執行 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">refresh</code>、<code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">plan</code>,然後 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">apply</code>。</li><li style="box-sizing: inherit; margin-bottom: 0px; margin-left: 1.25em;"><strong style="box-sizing: inherit;">destroy</strong>:清除資源或基礎設施。一種可能的使用場合是在進行某個產品的展示之後,將展示用的環境清除。此命令類似 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">apply</code>,它也會自動執行 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">refresh</code> 來獲取當前狀態,然後執行 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">plan</code> 來決定需要清理的資源以及清理的順序,最後再逐一執行清理工作。</li></ul><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">看完前面的介紹,應該就能理解底下這張圖所描繪的 Terraform 工作流程:</p><figure style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin: 1em auto; max-width: 100%; text-align: center; width: fit-content;"><p style="box-sizing: inherit;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/terraform/images/terraform-workflow.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/terraform/images/terraform-workflow.png" style="border-style: none; box-sizing: inherit; display: block; height: auto; max-width: 100%;" /></a></p><figcaption style="box-sizing: inherit; font-style: italic; margin: 1em auto; max-width: 24rem;">圖片來源:Terraform 官方網站</figcaption></figure><div class="admonition info" style="border-radius: 0.2rem; border: 0.05rem solid rgb(0, 184, 212); box-shadow: var(--md-shadow-z1); box-sizing: inherit; break-inside: avoid; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 0.7rem; margin: 1.5625em 0px; padding: 0px 0.6rem;"><p class="admonition-title" style="background-color: rgba(0, 184, 212, 0.1); border-bottom: none; border-image: initial; border-left: 0.2rem none; border-right: none; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; border-top: none; box-sizing: border-box; font-weight: 700; margin: 0px -0.6rem; padding: 0.4rem 0.6rem 0.4rem 2rem; position: relative;">Info</p><p style="box-sizing: border-box; margin-bottom: 0.6rem;">參考官方文件以了解更多命令與說明:<a href="https://developer.hashicorp.com/terraform/cli/commands" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">Basic CLI Features</a></p></div><h2 id="_5" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">結語</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">透過本文的簡短介紹,應可理解 Terraform 是一個用來建立與管理基礎設施的工具,同時它也是個「宣告式」 IaC 工具,亦即只要在組態檔中描述需要達成的結果或狀態,Terraform 就會自行判斷需要執行哪些工作。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">宣告式 IaC 的好處是:</p><ul style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">每當需要調整既有的基礎設施,只要修改組態檔就行了。例如原本是 5 台虛擬機器,後來在組態檔案中改成 7 台,Terraform 就能比對組態檔與現行狀態的差異,然後執行必要的操作。</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">相較於命令式(imperative)語句,採用宣告式語法的組態檔內容通常更簡潔、易懂。</li><li style="box-sizing: inherit; margin-bottom: 0px; margin-left: 1.25em;">DevOps 工程師總是能清楚知道目前基礎設施的狀態,因為 Terraform 會讓基礎設施的狀態維持跟組態檔中描述的狀態一致。</li></ul><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">此外,在比較複雜的作業環境中,DevOps 團隊可能會使用多種雲端平台和不同的技術,而 Terraform 能夠串接眾多平台,故可成為單一的 IaC 工具,而不需要 DevOps 團隊去學習使用各家平台自行提供的 IaC 工具(例如 Amazon 的 CloudFormation),這也是 Terraform 的優點之一。</p><h2 id="_6" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">參考資料</h2><ul style="background-color: white; box-sizing: inherit; display: flow-root; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 0.5em; margin-left: 1.25em;"><a href="https://developer.hashicorp.com/terraform/tutorials" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">Terraform 官方教學文件</a></li><li style="box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 0.5em; margin-left: 1.25em;"><a href="https://www.digistore24.com/redir/350808/user24310372/CAMPAIGNKEY" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">DevOps Bootcamp by Nana</a></li><li style="box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 0.5em; margin-left: 1.25em;"><a href="https://spacelift.io/blog/terraform-tutorial" target="_blank">Terraform Tutorial – Getting Started With Terraform on AWS</a></li></ul>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-92201593669929514952023-07-09T21:34:00.008+08:002023-07-11T22:54:52.522+08:00Kubernetes 簡介<p>介紹 Kubernetes 的入門基礎觀念。</p><span><a name='more'></a></span><p><br /></p><p>本文轉載自 <a href="https://huanlin.github.io/devops-notes/" target="_blank">DevOps 小學堂</a>,也就是我用來存放 DevOps 學習筆記的地方。</p><h2 id="_1" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">背景知識</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">讀者至少需要對以下技術名詞有基本概念:</p><ul style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">虛擬機器(virtual machine;VM)</li><li style="box-sizing: inherit; margin-bottom: 0px; margin-left: 1.25em;">容器(container)</li></ul><h2 id="_2" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">概述</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">隨著應用程式的設計方式從單體(monolith)逐漸轉變成微服務(microservices),DevOps 工程師需要管理的容器愈來愈多,架構也愈來愈複雜。為了應付日益繁重的開發與維運工作,Google 設計了一套工具,並且在公司內部使用了 15 年之後開放原始碼,捐給 CNCF (Cloud Native Computing Foundation)。該專案便是 Kubernetes 的前身。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">大體而言,Kubernetes 提供了以下好處:</p><ul style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">方便管理多個容器。</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">高可用性(high availablity),zero downtime 部署。</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">自我修復。</li><li style="box-sizing: inherit; margin-bottom: 0px; margin-left: 1.25em;">擴展性(scalability)。</li></ul><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">那麼,什麼是 Kubernetes?</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">首先來看官方網站的一段簡短介紹:</p><div class="admonition quote" style="border-radius: 0.2rem; border: 0.05rem solid rgb(158, 158, 158); box-shadow: var(--md-shadow-z1); box-sizing: inherit; break-inside: avoid; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 0.7rem; margin: 1.5625em 0px; padding: 0px 0.6rem;"><p class="admonition-title" style="background-color: rgba(158, 158, 158, 0.1); border-bottom: none; border-image: initial; border-left: 0.2rem none; border-right: none; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; border-top: none; box-sizing: border-box; font-weight: 700; margin: 0px -0.6rem; padding: 0.4rem 0.6rem 0.4rem 2rem; position: relative;">Quote</p><p style="box-sizing: border-box;"><a href="https://kubernetes.io/docs/concepts/overview/" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">Kubernetes</a>, also known as K8s, is an open-source system for automating deployment, scaling, and management of containerized applications.</p><p style="box-sizing: border-box; margin-bottom: 0.6rem;">中文:Kubernetes 又稱為 K8s,是個開源系統,用來自動部署、擴展、和管理容器應用程式。</p></div><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">這段介紹對於剛入門學習的新手來說可能過於精簡,不妨搭配一個常見的比喻來協助理解:交響樂團。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">K8s 就像是一個交響樂團的指揮,而演奏各種樂器的樂團成員則像是提供各種服務的容器。樂團成員(容器)必須聽從指揮(K8s)的安排和指示來演奏。如果樂團成員臨時出狀況而無法正常演出,指揮可能會指派其他人來替補,以確保演奏順利成功。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">簡單來說,K8s 能夠讓我們以宣告的方式來定義<strong style="box-sizing: inherit;">叢集</strong>(cluster)的狀態。所謂的「宣告」方式,就是以文字來描述我們想要達成什麼結果。那什麼是叢集呢?接著以一個簡單的例子來認識叢集,以及 K8s 的其他重要元件。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">假設原本的作業環境只有一個容器在運行,而我們希望讓它變成兩個容器,如下圖。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/k8s/overview/k8s-desired-state.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/k8s/overview/k8s-desired-state.png" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">我們可以用一個純文字檔案來描述你想要以怎樣的配置來運行整個系統(內容通常以 yaml 語法編寫),而 K8s 就會依照檔案中的指示來進行調配,以確保整體環境符合我們期望的狀態。這便是 K8s 能替我們做的事。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">一言以蔽之,K8s 的主要功能就是:讓系統環境以我們想要的方式來運行(desired state management)。</p><h2 id="master-nodeworker-nodecluster" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">Master Node、Worker Node、Cluster</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">欲實現上述情境,自然需要多台主機來讓 K8s 發揮其功能。主機可以是實體機或目前更常見的虛擬機,統稱為「節點」(node)。實務上,我們會有一個或多個<strong style="box-sizing: inherit;">主要節點</strong>(master nodes)來擔任管理的角色,管理誰呢?管理其他負責各種任務的節點,即所謂的「<strong style="box-sizing: inherit;">工作節點</strong>」(worker nodes)。每個工作節點包含一個或多個容器應用程式,而這一群工作節點以及主節點集合起來,便是所謂的叢集(Cluster)。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><div class="admonition note" style="border-radius: 0.2rem; border: 0.05rem solid rgb(68, 138, 255); box-shadow: var(--md-shadow-z1); box-sizing: inherit; break-inside: avoid; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 0.7rem; margin: 1.5625em 0px; padding: 0px 0.6rem;"><p class="admonition-title" style="background-color: rgba(68, 138, 255, 0.1); border-bottom: none; border-image: initial; border-left: 0.2rem none; border-right: none; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; border-top: none; box-sizing: border-box; font-weight: 700; margin: 0px -0.6rem; padding: 0.4rem 0.6rem 0.4rem 2rem; position: relative;">Note</p><p style="box-sizing: border-box; margin-bottom: 0.6rem;">叢集(Cluster):由 Kubernetes 管理的一群節點,這些節點通常會運行一些容器化應用程式。多數情況下,叢集中的節點並不會公開暴露於網際網路。</p></div><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/k8s/overview/k8s-big-picture.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/k8s/overview/k8s-big-picture.png" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">一旦主要節點故障,整個叢集當中的所有工作節點也將無法正常運行,故實務上會配置兩個或更多的主要節點。此外,工作節點通常負載較重、需要較多資源,因為它們要運行多個容器應用程式。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><div class="admonition info" style="border-radius: 0.2rem; border: 0.05rem solid rgb(0, 184, 212); box-shadow: var(--md-shadow-z1); box-sizing: inherit; break-inside: avoid; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 0.7rem; margin: 1.5625em 0px; padding: 0px 0.6rem;"><p class="admonition-title" style="background-color: rgba(0, 184, 212, 0.1); border-bottom: none; border-image: initial; border-left: 0.2rem none; border-right: none; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; border-top: none; box-sizing: border-box; font-weight: 700; margin: 0px -0.6rem; padding: 0.4rem 0.6rem 0.4rem 2rem; position: relative;">Info</p><p style="box-sizing: border-box; margin-bottom: 0.6rem;">這裡的 Master Node 在官方文件中比較常用的對等名詞是 Control Plane(控制平面)。</p></div><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">現在我們已經初步認識了 K8s 的三個基礎元件:Master Node、Worker Node、Cluster。接著來看另一個重要的元件:Pod。</p><h2 id="pod" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">Pod</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">Pod 是容器的 host,也是配置與部署的最小單位,因為我們(DevOps 工程師)在 K8s 叢集中配置或部署的對象都是 Pods,而不是直接去處理容器。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">由此可見,Pod 就像是一個抽象層,裡面包覆著容器。你也可以把 Pod 理解成一個箱子,而這個箱子裡面裝著容器。實務上比較常見的情形是一個 Pod 裡面只裝載一個容器,但也可以裝載多個容器。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/k8s/overview/k8s-pod-containers.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/k8s/overview/k8s-pod-containers.png" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">對於一個 Pod 僅裝載一個容器的場景,你甚至可以把它想像成一個穿著太空衣的太空人:Pod 就如太空衣,而真正執行各項任務的是太空衣裡面的人,亦即容器。那麼,多位太空人(Pods)彼此之間要如何溝通呢?它們需要倚賴 K8s 叢集裡面的另一個基礎元件:<strong style="box-sizing: inherit;">虛擬網路</strong>(virtual network)。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">K8s 叢集中的虛擬網路會給每一個 Pod 指派專屬的 IP 位址。也就是說,每一個 Pod 就像是一個伺服器,擁有自己的 IP 位址。各個 Pods 之間便可透過內部的 IP 位址來相互溝通。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/k8s/overview/pod-virtual-network.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/k8s/overview/pod-virtual-network.png" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">剛才提過,我們並不會直接去處理 K8s 叢集當中的容器,而是對 Pods 來進行相關操作。一旦 pod 當中的某個容器故障了,或者完全停擺,我們也須介入,因為 Pod 當中的容器將會自動重啟。那麼,如果是 Pod 本身掛掉呢?此時 K8s 會替那個掛掉的 Pod 再建立一個新的執行個體。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">然而,每當一個新的 Pod 被建立起來,它就會有一個新的 IP 位址,這會造成一些困擾。舉例來說,假設有一個 Pod A 是提供資料庫伺服器的功能,而其他 Pods 是透過 IP 位址來存取 Pod A 的資料庫,那麼每當 Pod A 因為故障而重新建立,IP 位址隨之改變,就會導致其他 Pods 無法存取資料庫。針對這種情形,我們必須認識 K8s 的另一個元件:Service。</p><h2 id="service" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">Service</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">這裡的 Service 並非平常泛指的軟體服務,而是 Kubernetes 用來協助 Pods 之間彼此溝通的連接機制。我們可以想像每個 Pod 都連接了一 個 Service 來當作對外聯繫的窗口,故彼此不再需要倚賴 Pod 的 IP 位址,而是透過這些服務來進行溝通。Service 和 Pod 之間的關係,是有點黏而又不太黏的——意思是二者之間雖有連結,但生命週期並不相同。當某個 Pod 因為故障而重新建立並獲取新的IP位址,原先與之相連的那個 Service 並不會消失或重建,而是持續存在。於是,各 Pod 之間透過這些 Services 來當作聯繫窗口,即使某個 Pod 因為故障而頻繁重建,也不會因為 IP 位址變更而令系統停擺。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/k8s/overview/k8s-pod-service.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/k8s/overview/k8s-pod-service.png" style="border-style: none; box-sizing: inherit; height: auto; max-width: 100%;" /></a></p><div class="admonition info" style="border-radius: 0.2rem; border: 0.05rem solid rgb(0, 184, 212); box-shadow: var(--md-shadow-z1); box-sizing: inherit; break-inside: avoid; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 0.7rem; margin: 1.5625em 0px; padding: 0px 0.6rem;"><p class="admonition-title" style="background-color: rgba(0, 184, 212, 0.1); border-bottom: none; border-image: initial; border-left: 0.2rem none; border-right: none; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; border-top: none; box-sizing: border-box; font-weight: 700; margin: 0px -0.6rem; padding: 0.4rem 0.6rem 0.4rem 2rem; position: relative;">Info</p><p style="box-sizing: border-box; margin-bottom: 0.6rem;">你可能也想看看:<a href="https://kubernetes.io/docs/concepts/services-networking/service/" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">官方文件對 Service 的說明</a></p></div><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">與 Pod 類似,Kubernetes 也會給每一個 Service 指派一個 IP 位址,只是派給 Service 的 IP 位址是固定的,不像 Pods 那樣很容易因為重建而頻繁變動 IP 位址。然而,如果你的應用程式會以 HTTP 協定的方式提供外部存取,我們當然不希望用戶端以類似 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">http://10.20.152.3:8080</code> 的位址來存取應用程式的服務,而會希望提供更友善的網址,例如 <code style="-webkit-box-decoration-break: clone; -webkit-tap-highlight-color: transparent; background-color: var(--md-code-bg-color); border-radius: 0.1rem; box-sizing: inherit; color: var(--md-code-fg-color); direction: ltr; font-family: var(--md-code-font-family); font-feature-settings: "kern"; font-size: 0.85em; font-variant-ligatures: none; outline: none; padding: 0px 0.294118em; word-break: break-word;">https://my-app.xyz.com</code>,這個部份則是由 Kubernetes 的另一個元件來處理:Ingress。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">當外界要存取 Kubernetes 叢集中的應用程式服 務時,Ingress 會負責將來自外部的請求(通常是 HTTP 或 HTTPS 請求)轉介至對應的目標服務。其運作方式可參考下圖:</p><figure style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin: 1em auto; max-width: 100%; text-align: center; width: fit-content;"><p style="box-sizing: inherit;"><a class="glightbox" data-desc-position="bottom" data-height="auto" data-type="image" data-width="100%" href="https://huanlin.github.io/devops-notes/k8s/overview/k8s-ingress.png" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;"><img alt="" src="https://huanlin.github.io/devops-notes/k8s/overview/k8s-ingress.png" style="border-style: none; box-sizing: inherit; display: block; height: auto; max-width: 100%;" /></a></p><figcaption style="box-sizing: inherit; font-style: italic; margin: 1em auto; max-width: 24rem;">圖片來源:kubernetes.io</figcaption></figure><h2 id="_3" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">結語</h2><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">本文簡單介紹了 Kubernetes(K8s)的由來、優點,以及它的四個主要元件:Master Node、Worker Node、Cluster、和 Pod,同時也提到了 K8s 中的虛擬網路、Pods 之間的溝通方式,以及 Service 和 Ingress 元件的用途。</p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;"><br /></p><p style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px;">K8s 還有許多元件沒有在文中介紹,例如 Master Node 當中還有 API Server、Controller Manager、Scheduler、etcd 等等。如欲進一步了解,可參考官方網站的文件或文後列出的參考資料。</p><h2 id="_4" style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 1.5625em; letter-spacing: -0.01em; line-height: 1.4; margin: 1.6em 0px 0.64em;">參考資料</h2><ul style="background-color: white; box-sizing: inherit; color: rgba(0, 0, 0, 0.87); display: flow-root; font-family: "Noto Sans TC", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 17.6px; margin-bottom: 1em; margin-left: 0.625em; margin-top: 1em; padding: 0px;"><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;">https://kubernetes.io/</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;"><a href="https://kubernetes.io/docs/concepts/overview/" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">Kubernetes Overview</a></li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;"><a href="https://app.pluralsight.com/courses/bea52e4a-38de-4ba1-8aa4-7787e2edb9a6/table-of-contents" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">Kubernetes for Developers: Core Concepts</a> by Dan Wahlin</li><li style="box-sizing: inherit; margin-bottom: 0.5em; margin-left: 1.25em;"><a href="https://www.youtube.com/watch?v=VnvRFRk_51k" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">What is Kubernetes | Kubernetes explained in 15 mins</a> by Nana</li><li style="box-sizing: inherit; margin-bottom: 0px; margin-left: 1.25em;"><a href="https://cwhu.medium.com/kubernetes-basic-concept-tutorial-e033e3504ec0" style="-webkit-tap-highlight-color: transparent; box-sizing: inherit; text-decoration-line: none; transition: color 125ms ease 0s; word-break: break-word;">Kubernetes 基礎教學(一)原理介紹</a> by 胡程維</li></ul>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-26546568235218342362023-06-12T22:00:00.018+08:002023-06-17T10:22:45.760+08:00Tom Johnson 的技術寫作經驗談<p>重點整理技術寫手 Tom Johnson 的兩段訪談。</p><span><a name='more'></a><br /></span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiMLUbilvnF7kvmogso7Lp6pUbLZlnnj1tWRioBiZnF10v7Z5XFgWUyyr_QTEMh75ocZy4LgRvR4UIXMAPj3yCzAbtYjxerHuyAJ8EbSuqiFerVbnwXBZGpl-wVx3PaAFV5j3hhVRtMriPMmVoquWBZxY12c-5JNa_tWgDJsbzslMY-M8pFjpCNKuQ/s640/2023-06-17_10-10-42.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="365" data-original-width="640" height="228" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiMLUbilvnF7kvmogso7Lp6pUbLZlnnj1tWRioBiZnF10v7Z5XFgWUyyr_QTEMh75ocZy4LgRvR4UIXMAPj3yCzAbtYjxerHuyAJ8EbSuqiFerVbnwXBZGpl-wVx3PaAFV5j3hhVRtMriPMmVoquWBZxY12c-5JNa_tWgDJsbzslMY-M8pFjpCNKuQ/w400-h228/2023-06-17_10-10-42.png" width="400" /></a></div><br /><p>這兩段訪談分別是:</p><p></p><ul style="text-align: left;"><li><a href="https://document360.com/blog/amazons-tom-johnson-shares-his-technical-writing-knowledge/" target="_blank">Technical Documentation with Tom Johnson of Amazon</a>(2022-2-25)</li><li><a href="https://www.youtube.com/watch?v=hDGIjU5Hzvc" target="_blank">Tom Johnson: Documenting APIs | Episode 088</a>(2020-12-4)</li></ul><p></p><div><div style="text-align: left;">底下是第一段訪談的重點概要。</div><h2 style="text-align: left;">關於 Tom Johnson</h2><div>Tom 目前在 Amazon 擔任技術寫手(technical writer),團隊成員共兩人(包含 Tom),他認為小團隊的好處就是彈性、靈活。</div><blockquote><div>註:也許有人覺得用「技術作家」這個詞顯得更尊重、更好聽一些。這裡用「技術寫手」絲毫沒有貶抑的意思,純粹個人慣用偏好。</div></blockquote><div><br /></div><h2 style="text-align: left;">Agile 文件寫作流程</h2><div>Tom 的文件小組採用 <a href="https://document360.com/blog/best-way-to-process-documentation-with-kavitha-kandappan-of-amd/" target="_blank">Agile 文件寫作流程</a>,每一個衝刺(sprint)為期兩週,並於衝刺結束前展示成果。</div><div><br /></div><div>Tom 還採用了五個 review 步驟:</div><div><ul style="text-align: left;"><li>Internal review(內部審閱)</li><li>Product team review(產品團隊審閱)</li><li>Field and support engineer review</li><li>Legal review</li><li>Beta partner review</li></ul></div><div><br /></div><div>經歷上述五個審閱程序的技術文件通常都有不錯的品質。</div><div><br /></div><h2 style="text-align: left;">Docs As Code 流程</h2><div>Tom 認為 <a href="https://idratherbewriting.com/learnapidoc/pubapis_docs_as_code.html" target="_blank">Docs as Code</a> 流程應具備以下五個特徵:</div><div><ul style="text-align: left;"><li>文件格式:使用 markdown 來編寫文件原稿。</li><li>內容管理:使用 Git 來進行文件的版本控管。</li><li>部署方式:持續部署(至 Git)。</li><li>文字編輯器,例如 Visual Studio Code。(按:我認為原文的 "Test editor" 很可能是 Document360 網站小編的誤解)</li><li>使用靜態網站產生器來輸出最終成品。(按:例如 Sphinx、Jekyll、Hugo 等等)</li></ul></div><blockquote><div>補充:只要在 GitHub 上面修改文件的原始碼(原稿),推送至儲存庫之後就能夠自動觸發文件的建置腳本(包含自動檢查文件中的無效連結、不一致的術語、格式等等)。</div></blockquote><div><br /></div><h2 style="text-align: left;">技術文件的 ROI</h2><div>Tom 認為許多文件寫作團隊過於倚賴度量數據來評斷文件的品質。比如說,以客戶或技術支 援團隊使用文件的情形來評斷技術文件的 ROI,這種作法有以下問題:</div><div><ul style="text-align: left;"><li>如何得知有多少人看了文件卻沒有跟技術支援團隊聯繫?</li><li>技術支援團隊如何知道自己是因為先前讀了某份技術文件才擁有解決某個問題的知識?</li><li>如何得知有多少客戶是因為看到文件之後(按:覺得文件寫太差)而拒絕某個專案?</li><li>如何衡量有多少商業機會是因為技術文件產生的附帶效應、以及實際影響程度多少?</li><li>如何衡量新進員工透過技術文件而縮短了多少「上手時間」(ramp up time) ?</li></ul></div><blockquote><div>按:總之,技術文件所產生的價值與回報難以估算。一種常見的方法是在每一份文件當中提供使用者評分機制和提交反饋意見的地方,例如留言板,或者 issue tracker 連結。</div></blockquote><p> </p><h2 style="text-align: left;">技術寫手經常面臨的挑戰</h2><div>本段來自第二個訪談:<a href="https://www.youtube.com/watch?v=hDGIjU5Hzvc" target="_blank">Tom Johnson: Documenting APIs | Episode 088</a>,只記下我看到覺得特別有共鳴的地方。</div><div><br /></div><div>以下開始:</div><div><br /></div><div><div>通常技術寫手不是開發者,比較沒有專業的架構設計和工程背景,所以每當碰到需要撰寫技術文件的時候,例如某個 Web API 專案的文件,技術寫手就得忙著趕上與該專案相關的技術,並斟酌該深入到多細的架構設計細節,才足以寫出令目標讀者覺得好讀、有幫助的技術文件。</div><div><br /></div><div>但如果能看到 API 使用者是如何去使用那些 API 來完成特定 use cases,技術寫手便能比較容易看得出來那些 API 是怎麼彼此兜起來完成一件工作,以及他們的用法是否簡單好用、有哪些值得改進之處,並且更知道該怎麼去解釋與說明那些 API 的用法,才能讓目標讀者覺得閱讀那份技術文件是有幫助的——正好能解決他們的問題,或者節省了他們學習摸索的時間。</div><div><br /></div><div>因此,為了寫出清晰易讀且內容正確的技術文件,技術寫手通常需要與開發人員密切合作, 以獲取他/她所需要了解的資訊,包括相關的架構、技術,而且通常需要開發人員提供 code samples。此外,技術寫手可能還需要對開發人員提供的 code samples 進行測試,以確保它們都能產生預期的結果。技術文件也需要相關人員審閱(包括開發人員和其他目標讀者),並提供反饋意見,以便持續完善文件內容。</div><div><br /></div>
<hr />
<div>我最近經常在想這些問題,所以在整理重點的時候,難免添加了一些自己的話來延伸和補充,其實 Tom Johnson 在第二段訪談裡面並沒有說那麼多。</div></div><div><br /></div><div>Keep writing!</div></div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-72810293692147174982023-04-11T13:13:00.026+08:002023-04-15T14:41:16.638+08:00讓 Obsidian 以新頁籤來開啟檔案<p>摘要:讓 Obsidian 預設以新頁籤來開啟檔案的幾個方法。</p><span></span><span><a name='more'></a></span><p><br /></p><h2 dir="auto" style="background-color: white; border-bottom: 1px solid var(--color-border-muted); box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;">問題描述</h2><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">使用 Obsidian 的時候,我常碰到一個小小的困擾:每當以滑鼠左鍵點擊檔案面板中的某個筆記,或點擊筆記中的某個內部連結時,Obsidian 會使用目前的頁籤(tab)來開啟目標檔案。換言之,原本我目前正在編輯區查看或編輯的檔案,會被換成我剛剛點擊的檔案。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">我發現幾乎每一次執行上述操作,我都會不自覺地以為 Obsidian 會用一個新的頁籤來開啟那個檔案。然後當我發現目前編輯的檔案被替換成另一個檔案時,我只好透過【Ctrl+Alt+左方向鍵】返回上一頁來再次開啟剛才編輯的檔案。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">雖然只是操作上的一個小小不便,但是發生的頻率頗高,還是會造成一定程度的困擾。我在<a href="https://forum.obsidian.md/t/click-links-files-to-open-in-new-tab-by-default/7347/90" target="_blank">網路論壇</a>上面也看到許多人敲碗,希望官方能為 Obsidian 提供一個選項,讓使用者選擇要不要使用新頁籤來開啟檔案與內部連結。</p><h2 dir="auto" style="background-color: white; border-bottom: 1px solid var(--color-border-muted); box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a aria-hidden="true" class="anchor" href="https://github.com/huanlin/allnotes/blob/main/Mastermind/5-%E7%94%9F%E7%94%A2%E5%8A%9B/Obsidian/Obsidian-opener%20%E5%A4%96%E6%8E%9B%EF%BC%9A%E9%BB%9E%E6%BB%91%E9%BC%A0%E5%B7%A6%E9%8D%B5%E5%8F%AF%E5%B0%87%E6%AA%94%E6%A1%88%E9%96%8B%E5%9C%A8%E6%96%B0%E9%A0%81%E7%B1%A4.md#%E5%85%A7%E5%BB%BA%E7%9A%84%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%B3%95" id="user-content-內建的解決方法" style="background-color: transparent; box-sizing: border-box; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a>內建的解決方法</h2><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">Obsidian 在設計時已經有考慮到使用者可能需要以新頁籤來開啟目標檔案,故提供了兩種操作方法來解決剛才的問題:</p><ul dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px; padding-left: 2em;"><li style="box-sizing: border-box;">Ctrl+滑鼠左鍵</li><li style="box-sizing: border-box; margin-top: 0.25em;">滑鼠的中鍵(或滾輪)</li></ul><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">這兩種操作方法都能解決問題,我更偏好以滑鼠中鍵來操作,因為只需要動用一隻手,更簡便。如果滑鼠沒有中鍵或滾輪可以按,自然就只能選擇【Ctrl+滑鼠左鍵】的方式了。然而,儘管我知道【Ctrl+滑鼠左鍵】可以讓 Obsidian 把目標檔案開啟在新頁籤,但我發現每次操作的時候總是沒預先想到要按住 Ctrl 鍵。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">如果你對上述按鍵操作方式都不滿意,仍希望用滑鼠左鍵就能把檔案開啟在新頁籤,則可以嘗試安裝社群外掛。</p><h2 dir="auto" style="background-color: white; border-bottom: 1px solid var(--color-border-muted); box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a aria-hidden="true" class="anchor" href="https://github.com/huanlin/allnotes/blob/main/Mastermind/5-%E7%94%9F%E7%94%A2%E5%8A%9B/Obsidian/Obsidian-opener%20%E5%A4%96%E6%8E%9B%EF%BC%9A%E9%BB%9E%E6%BB%91%E9%BC%A0%E5%B7%A6%E9%8D%B5%E5%8F%AF%E5%B0%87%E6%AA%94%E6%A1%88%E9%96%8B%E5%9C%A8%E6%96%B0%E9%A0%81%E7%B1%A4.md#%E4%BD%BF%E7%94%A8%E7%A4%BE%E7%BE%A4%E5%A4%96%E6%8E%9B" id="user-content-使用社群外掛" style="background-color: transparent; box-sizing: border-box; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a>使用社群外掛</h2><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">我找到的相關社群外掛有兩個:<a href="https://github.com/darlal/obsidian-switcher-plus" target="_blank">Quick Switcher++</a> 和 <a href="https://github.com/aidan-gibson/obsidian-opener" target="_blank">Obsidian-opener</a>。本文發布後,有人在文章下方留言告知還有一個外掛也可以:<a href="https://github.com/patleeman/obsidian-open-in-new-tab" target="_blank">Open In New Tab</a>(感謝網友 Jerry Chien 提供!),於是我也一併測試了一下,並加入本文。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">接著簡單介紹這三個外掛。</p><h3 dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px;">Quick Switcher++ 外掛</h3><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">從 <a href="https://github.com/darlal/obsidian-switcher-plus" target="_blank">Quick Switcher++</a> 的官方說明文件得知它的功能蠻多,而將檔案開啟於新頁籤只是其中一項。出於「功能越多可能越好」的心理,我先安裝了 Quick Switcher++(版本為 3.0.0)。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">安裝完成後,記得要啟用此外掛,並確認此外掛的 Default to open in new tab 選項是 enable 的狀態。如下圖:</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMhD5Vttmfm3l5CYznvX2y5zcg5Bfchh6W02SXg4Nv6w7spvFePp64UK7PVu1w_YOCAHWcHwpNEHTZiZcEki05HwjAqnAASiIJHQA0oWLCpEa84TLKv_CVo46Wb0q8PmSs9YOk_jom0qRO2UXdfMr7qYJNpQghqrmJLT8_Uyqk4tF0u_kkGMD2b0nQ/s961/quickswitcher-settings.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="764" data-original-width="961" height="508" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMhD5Vttmfm3l5CYznvX2y5zcg5Bfchh6W02SXg4Nv6w7spvFePp64UK7PVu1w_YOCAHWcHwpNEHTZiZcEki05HwjAqnAASiIJHQA0oWLCpEa84TLKv_CVo46Wb0q8PmSs9YOk_jom0qRO2UXdfMr7qYJNpQghqrmJLT8_Uyqk4tF0u_kkGMD2b0nQ/w640-h508/quickswitcher-settings.png" width="640" /></a></div><br /><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><h3 dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px;">Open In New Tab 外掛</h3><div><span face="-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"" style="background-color: white; color: #1f2328; font-size: 16px;">相較於 Quick Switcher++ 的多樣功能,</span><span face="-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"" style="background-color: white; color: #1f2328; font-size: 16px;"><a href="https://github.com/patleeman/obsidian-open-in-new-tab" target="_blank">Open In New Tab</a> 只專注於一件事:點滑鼠左鍵就能以新頁籤</span><span face="-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"" style="background-color: white; color: #1f2328; font-size: 16px;">開啟目標檔案或內部連結。</span></div><div><br /></div><div><span face="-apple-system, BlinkMacSystemFont, Segoe UI, Noto Sans, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji" style="color: #1f2328;"><span style="background-color: white;">安裝方法很簡單,直接在 Obsidian 設定視窗中的 Community plugins 頁籤,透過瀏覽功能先找到這個外掛,然後點選安裝。此外掛沒有任何選項,一經啟用,即可生效。(版本:1.0.9)</span></span></div><h3 dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px;"><a aria-hidden="true" class="anchor" href="https://github.com/huanlin/allnotes/blob/main/Mastermind/5-%E7%94%9F%E7%94%A2%E5%8A%9B/Obsidian/Obsidian-opener%20%E5%A4%96%E6%8E%9B%EF%BC%9A%E9%BB%9E%E6%BB%91%E9%BC%A0%E5%B7%A6%E9%8D%B5%E5%8F%AF%E5%B0%87%E6%AA%94%E6%A1%88%E9%96%8B%E5%9C%A8%E6%96%B0%E9%A0%81%E7%B1%A4.md#obsidian-opener-%E5%A4%96%E6%8E%9B" id="user-content-obsidian-opener-外掛" style="background-color: transparent; box-sizing: border-box; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a>Obsidian-opener 外掛</h3><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><a href="https://github.com/aidan-gibson/obsidian-opener" target="_blank">Obsidian-opener</a> 的功能也很單純,只有兩個:以新頁籤開啟檔案或內部連結,以及使用預設的 app 來開啟 pdf 檔案。不過,此外掛目前仍在 beta 測試階段,僅能透過 <a href="https://github.com/TfTHacker/obsidian42-brat" target="_blank">BRAT</a> 來安裝。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">BRAT 的全名是 Beta Reviewers Auto-update Tester,我們可以直接使用 Obsdian 內建的社群外掛視窗來搜尋和安裝 BRAT 外掛。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">安裝並啟用 BRAT 之後,在 Obsidian 中按 Ctrl+P 開啟命令視窗,並於搜尋框輸入「brat」來尋找相關命令:</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_T4lpePBpJeufBwyfAetAwQKgcpg-dGKlP2LRGrptZ6bPN0F5ObPCqD1B9C7If0B4Me7WveZRmSKC6nq_mN7giXFDRU4HIUpJgbBru6561_jbubCUKKDnVXoKOtJ0Ni8dH4mpeswPt4c1KRBSEDmxVsJWCH_PdAQ2HG5GVtZDli2aRtVFeVjLtveR/s842/command-palette-brat.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="729" data-original-width="842" height="554" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_T4lpePBpJeufBwyfAetAwQKgcpg-dGKlP2LRGrptZ6bPN0F5ObPCqD1B9C7If0B4Me7WveZRmSKC6nq_mN7giXFDRU4HIUpJgbBru6561_jbubCUKKDnVXoKOtJ0Ni8dH4mpeswPt4c1KRBSEDmxVsJWCH_PdAQ2HG5GVtZDli2aRtVFeVjLtveR/w640-h554/command-palette-brat.png" width="640" /></a></div><br /><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">選擇 BRAT: Plugins: Add a beta plugin for testing,然後它會開啟一個對話窗,要你輸入欲安裝之外掛的 GitHub 儲存庫網址,也就是 <a href="https://github.com/aidan-gibson/obsidian-opener" style="background-color: transparent; box-sizing: border-box; text-decoration-line: none;">https://github.com/aidan-gibson/obsidian-opener</a>。如下圖:</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLl8prDnuh9mnOPzIJ1K4cxE7_mTKFc5BTo1DB4oyBR0uN3MJqzy6Y6tkyVyMQZ5qmA9YAEE9lfFIPTILSd2qcR7CX3RmMSgg2T4APhVhbIqeXI76ezQPUgDcbzOtBm2iMihEwO_86_dEflXinFha2mQ00IxI95JnCYYgZWDQxLPZaVUMvBP3_xAY2/s672/brat-install-obsidian-opener.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="317" data-original-width="672" height="302" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLl8prDnuh9mnOPzIJ1K4cxE7_mTKFc5BTo1DB4oyBR0uN3MJqzy6Y6tkyVyMQZ5qmA9YAEE9lfFIPTILSd2qcR7CX3RmMSgg2T4APhVhbIqeXI76ezQPUgDcbzOtBm2iMihEwO_86_dEflXinFha2mQ00IxI95JnCYYgZWDQxLPZaVUMvBP3_xAY2/w640-h302/brat-install-obsidian-opener.png" width="640" /></a></div><br /><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">輸入外掛的網址,然後點 Add Plugin 按鈕,即可完成安裝。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">外掛安裝完成後,還必須啟用才行。請開啟 Obsidian 的設定視窗,選擇 Community plugins,然後找到 Opener 外掛,將它啟用。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgm6hCCjuJkLJgCdzJ2IiB9Ymb16Jf6Z2N2lB8LOKZ7quc_Bv_NOQxoURfEdjOzJPKDQyuJycwhhp9l9YFZ16bOw7aKYpBxIPfC6P6acvzHLF75TIMDfII_BRHnk51RTtIIEN2QxnDDbGc48CvaaVfOKlsU4hDltvACMXmPkbrLyIezXAYBO3k59F9x/s1324/plugins-enable-obsidian-opener.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="715" data-original-width="1324" height="346" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgm6hCCjuJkLJgCdzJ2IiB9Ymb16Jf6Z2N2lB8LOKZ7quc_Bv_NOQxoURfEdjOzJPKDQyuJycwhhp9l9YFZ16bOw7aKYpBxIPfC6P6acvzHLF75TIMDfII_BRHnk51RTtIIEN2QxnDDbGc48CvaaVfOKlsU4hDltvACMXmPkbrLyIezXAYBO3k59F9x/w640-h346/plugins-enable-obsidian-opener.png" width="640" /></a></div><br /><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">啟用 Obsidian-opener 外掛之後,只要點滑鼠左鍵,就會一律以新頁籤開啟目標檔案或內部連結了。如果目標檔案原本就已經開啟,則會切換至該檔案所屬的頁籤。前面介紹的兩個外掛也都有同樣的行為。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">最後要提醒的是,Obsidian-opener 外掛之所以需要透過 BRAT 來安裝,正是因為它還在 beta 測試階段,而且未來有可能因為 Obsidian 改版而導致它無法正常運作。為了避免這個狀況,建議把 BRAT 設定中的「Auto-Update Plugins at Startup」選項打開,以便隨時獲取 beta 外掛的最新版本。操作步驟如下圖:</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjp6NZiIwF58i3xWdVUNCh-eaEqG5y9o7EaG2KEeKyhKkW54LC_6HzQpZb-Zs3PI19Onw-1Zya0lE13IWR-p6XENxYcTXPMefUxVg4KGenIibJhgQqKQVlfx5opNtnSJghphNOd2UjoWIMHhWWyeeoWmzOeJ5ZzX0Fc6mkofAeaf2hCqCSP8DKjzKcs/s1321/brat-options.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="887" data-original-width="1321" height="430" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjp6NZiIwF58i3xWdVUNCh-eaEqG5y9o7EaG2KEeKyhKkW54LC_6HzQpZb-Zs3PI19Onw-1Zya0lE13IWR-p6XENxYcTXPMefUxVg4KGenIibJhgQqKQVlfx5opNtnSJghphNOd2UjoWIMHhWWyeeoWmzOeJ5ZzX0Fc6mkofAeaf2hCqCSP8DKjzKcs/w640-h430/brat-options.png" width="640" /></a></div><br /><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 0px; margin-top: 0px;">註:撰寫本文時,Obsidian 版本為 1.1.16,而 Obsidian-opener 外掛的版本為 1.0.30。</p><h2 dir="auto" style="background-color: white; border-bottom: 1px solid var(--color-border-muted); box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;">結語</h2><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 0px; margin-top: 0px;">本文介紹的三個外掛我都試過,都能達到相同目的,擇一即可。我比較喜歡的是 Open In New Tab 和 Obsidian-opener 的簡單設計,但後者必須透過 beta 管道來安裝,略嫌麻煩,故最終我只留下 Open In New Tab。如果有一天因為 Obsidian 改版而造成其中某一個外掛無法正常運作,至少還有兩個外掛可用。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 0px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 0px; margin-top: 0px;">萬一本文介紹的外掛都失效了,那就用 Obsdian 預設提供的滑鼠中鍵(滾輪)吧!說實在的,滑鼠左鍵和中鍵離得很近,操作上的差異不大,頂多就是手指按下左鍵和滾輪的觸感明顯不同吧。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 0px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 0px; margin-top: 0px;">Happy writing!</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 0px; margin-top: 0px;"><br /></p><h2 dir="auto" style="background-color: white; border-bottom: 1px solid var(--color-border-muted); box-sizing: border-box; color: #1f2328; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;">附錄:與導覽有關的按鍵與滑鼠操作</h2><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdp9da6L51slghNeJQmhk9xP0ycqh7s7sfHmvYm2-abX0N6btU1YEQSOiUorZfjSGIctCKZZi9xAQRDreVbi9PPM-J7jpayhUPq9DsRpIpJsJByIJXR9g2Yy-h4sFLgbQ-PbsozGUj276bhTzqCnltzQ10BhimBdlmF-KnPz35ePe-r4ncqxm6kHbm/s908/obsidian-open-with-new-tab.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="676" data-original-width="908" height="476" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdp9da6L51slghNeJQmhk9xP0ycqh7s7sfHmvYm2-abX0N6btU1YEQSOiUorZfjSGIctCKZZi9xAQRDreVbi9PPM-J7jpayhUPq9DsRpIpJsJByIJXR9g2Yy-h4sFLgbQ-PbsozGUj276bhTzqCnltzQ10BhimBdlmF-KnPz35ePe-r4ncqxm6kHbm/w640-h476/obsidian-open-with-new-tab.png" width="640" /></a></div><br /><div><br /></div><p></p>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-49304441312710053462023-03-15T11:50:00.012+08:002023-03-16T00:00:58.978+08:00Obsidian 初學者筆記<p>整理完 <a href="https://www.huanlintalk.com/2023/02/how-to-take-smart-notes.html" target="_blank">《卡片盒筆記》的讀書心得</a>,接著整理我使用 Obsidian 筆記工具的一點心得。文末附上我錄製的兩個 Youtube 影片連結,分別介紹卡片盒筆記和 Obsidian 的一些入門知識。</p><span><a name='more'></a></span><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdKYBxUodRLOTc2xu-nfdqPpRWy1RunK8AUax2ToNYlf_TLbdBB3fDl5J25aeYdo0FqTidUzL5cYVsga9X1FJotemOkluF_UvBZb2hIqrLEg1VJ8tO5JAWVBNDsQrCXb3pXpy9NC_W3lyd7il3AgZztr9vjs9gmlXl2GUlPwTF87fauoIJ355ciJKp/s640/obsidian%20%E5%88%9D%E5%AD%B8%E8%80%85%E7%AD%86%E8%A8%98-1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="357" data-original-width="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdKYBxUodRLOTc2xu-nfdqPpRWy1RunK8AUax2ToNYlf_TLbdBB3fDl5J25aeYdo0FqTidUzL5cYVsga9X1FJotemOkluF_UvBZb2hIqrLEg1VJ8tO5JAWVBNDsQrCXb3pXpy9NC_W3lyd7il3AgZztr9vjs9gmlXl2GUlPwTF87fauoIJ355ciJKp/s16000/obsidian%20%E5%88%9D%E5%AD%B8%E8%80%85%E7%AD%86%E8%A8%98-1.png" /></a></div><p><br /></p><h2 dir="auto" style="background-color: white; border-bottom: 1px solid var(--color-border-muted); box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;">前言</h2><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">當我知道「卡片盒筆記法」以及 Obsidian,我有一種如獲至寶的感覺。因為長期以來,我的筆記四處分散,包括部落格、GitHub、OneNote、Google 文件、Xmind、……等等,缺乏一個集中管理的地方。筆記分散各處的最大問題是:雖然寫了筆記,也保存了下來,可是將來有一天需要的時候,卻可能早已忘記寫在哪裡,甚至根本忘記自己曾經寫過這些東西。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">用來整理筆記的工具軟體相當多,除了剛才提的那幾個,我也調研過 Evernote、Notion、Logseq、Workflowy……等等,以及本文的主角:Obsidian。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">我喜歡 Obsidian 的地方有:</p><ul dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px; padding-left: 2em;"><li style="box-sizing: border-box;"><b>免費使用</b>,也有付費功能。免費版的功能就已經非常好用。</li><li style="box-sizing: border-box; margin-top: 0.25em;"><b>使用 Markdown 來寫筆記</b>,而且提供了一些增強語法,例如 callout(對話方塊、補充方塊),可以讓排版更加豐富靈活。程式碼區塊也能根據程式語言類型呈現不同的語法顏色。</li><li style="box-sizing: border-box; margin-top: 0.25em;"><b>離線編輯</b>,筆記全都保存在自己的本機磁碟,也能同步至雲端。我的作法是透過外掛設定每隔 60 分鐘自動將筆記同步至我的 GitHub repo,省心省力,非常方便。</li><li style="box-sizing: border-box; margin-top: 0.25em;"><b>彈性靈活</b>,可以輕鬆寫簡單的每日記錄,也可以盡情折騰,建構繁複龐雜的筆記。</li><li style="box-sizing: border-box; margin-top: 0.25em;">不強調分類,而是透過<b>雙向連結,由下而上</b>的方式來自然形成知識體系。如果想要由上而下的方式來組織分類,當然也沒問題。</li><li style="box-sizing: border-box; margin-top: 0.25em;">有許多好用的外掛,<b>擴充能力強</b>。</li></ul><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">我絕對稱不上「筆記控」,但我依然看重一件事:<b>既然花了時間寫筆記,這些筆記除了當下輔助學習和加強記憶,它們也必須在未來的某個時刻替我服務</b>——當我在腦中思索以前曾寫過的東西,必須要能夠很快找到它們。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">這就牽涉到挑選筆記工具的一個重要考量:如果將來某一天,我使用的筆記工具被市場淘汰了,或者發現另一款更好用的筆記工具,我能輕易跳船嗎?我原先辛苦整理的那些筆記,能夠完整、近乎無縫地移轉至新工具嗎?</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">使用 Obsidian 來撰寫的筆記都是純文字的 Markdown 檔案,而且全都放在我的電腦磁碟裡面,從而保障了筆記的私密性。如果需要備份還是同步至雲端,也很容易。我是透過一個叫做 Obsidian Git 的外掛來定時自動將筆記同步至我的 GitHub 私人儲存庫。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">如果將來有一天要放棄 Obsidian,只要新的筆記工具也採用 Makrdown 格式,便不至於出現什麼大問題。萬一將來真的碰到新工具在 Makrdown 增強語法或雙向連結的部分採用不同格式,只要寫一點小程式就能解決格式轉換的問題。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><blockquote style="background-color: white; border-left: 0.25em solid var(--color-border-default); box-sizing: border-box; color: var(--color-fg-muted); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin: 0px 0px 16px; padding: 0px 1em;"><p dir="auto" style="box-sizing: border-box; margin-bottom: 0px; margin-top: 0px;"><b>雲端筆記的問題</b>:雲端筆記有它的方便之處,但我難免擔心如果將來有一天要改用其他工具,那些放在雲端的筆記勢必得透過工具軟體本身提供的匯出功能,才能移轉並重複使用那些筆記。軟體廠商不喜歡用戶跳槽,而更想要黏住用戶,故我對雲端類型的筆記工具總是保持戒心。這不是杞人憂天,而是曾經發生在別人身上的慘痛教訓(導致他<a href="https://youtu.be/LGcmP3JHkuY" target="_blank">放棄了原先寫好的 4000 條筆記</a>)。</p></blockquote><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">接著來說說我是如何組織我的筆記,也就是檔案資料夾的結構。</p><h2 dir="auto" style="background-color: white; border-bottom: 1px solid var(--color-border-muted); box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;">組織 Vault 與資料夾</h2><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">Obsidian 是以 <span style="box-sizing: border-box; font-weight: var(--base-text-weight-semibold, 600);">vault</span> 來代表一群筆記的集合,有點類似 GitHub 儲存庫的概念。一個 vault 就是本機檔案系統中的一個資料夾,我們可以把全部的筆記都放在同一個 vault 裡面,也可以根據自己的需求和偏好來建立多個 vaults。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><blockquote style="background-color: white; border-left: 0.25em solid var(--color-border-default); box-sizing: border-box; color: var(--color-fg-muted); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin: 0px 0px 16px; padding: 0px 1em;"><p dir="auto" style="box-sizing: border-box; margin-bottom: 0px; margin-top: 0px;"><b>Vault</b> 這個單字有金庫、保險庫的意思。在需要以中文表達的時候,我會用「筆記庫」來指稱 vault。</p></blockquote><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">關於筆記庫(vault)的幾個知識點:</p><ul dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px; padding-left: 2em;"><li style="box-sizing: border-box;"><b>每個 vault 是各自獨立的</b>,這表示不同 vault 中的筆記無法建立雙向內部連結。「內部連結」是 Obsidian 的一個專有名詞,指的是一個筆記庫內的連結。</li><li style="box-sizing: border-box; margin-top: 0.25em;">每個 vault 有自己的設定,包括主題風格(themes)、外掛(plugins)等等,這些<b>設定是保存在一個名為 .obsidian 的資料夾</b>(在 macOS 平台可能是隱藏的資料夾)。</li><li style="box-sizing: border-box; margin-top: 0.25em;">如果想要在不同 vaults 之間使用相同的設定,只要複製 .obsidian 資料夾底下的全部檔案即可(需要重啟 Obsidian 讓設定生效)。</li><li style="box-sizing: border-box; margin-top: 0.25em;">雖然沒有禁止,但請<b>不要在一個 vault 底下建立巢狀 vault</b>,因為容易混淆,衍生不必要的麻煩。</li></ul><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">第一次開啟 Obsidian 時,必須建立或選擇一個資料夾來作為預設的 vault。如果需要相關操作步驟,可參考官方網站的說明:<a href="https://help.obsidian.md/Getting+started/Create+a+vault" rel="nofollow" style="background-color: transparent; box-sizing: border-box; text-decoration-line: none;">Getting started > Create a vault</a>。我自己也錄製了兩個入門教學影片,稍後會附上 Youtube 連結。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><blockquote style="background-color: white; border-left: 0.25em solid var(--color-border-default); box-sizing: border-box; color: var(--color-fg-muted); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin: 0px 0px 16px; padding: 0px 1em;"><p dir="auto" style="box-sizing: border-box; margin-bottom: 0px; margin-top: 0px;">初次建立筆記庫時,可能一時之間不知道要給它取什麼名字。不妨考慮簡單直觀的 MyNotes、ThinkTank、私人筆記、工作筆記……等等。</p></blockquote><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">我的筆記庫至少會有以下幾個資料夾,每個資料夾前面加上數字,以便控制它們的顯示順序:</p><ul dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px; padding-left: 2em;"><li style="box-sizing: border-box;">0-日誌:即 Obsidian 裡面的 daily note,每一篇筆記皆以日期來命名。</li><li style="box-sizing: border-box; margin-top: 0.25em;">1-閃念筆記:用來存放任何臨時的想法、點子,或從其他地方擷取而來的片段。也就是<a href="https://www.huanlintalk.com/2023/02/how-to-take-smart-notes.html" target="_blank">卡片盒筆記法</a>裡面說的 fleet note,常見的中文翻譯是「靈感筆記」、「快閃筆記」、或「臨時筆記」。</li><li style="box-sizing: border-box; margin-top: 0.25em;">2-文獻筆記:即<a href="https://www.huanlintalk.com/2023/02/how-to-take-smart-notes.html" target="_blank">卡片盒筆記法</a>的 literature notes。</li><li style="box-sizing: border-box; margin-top: 0.25em;">X-主題索引:用來組織特定主題的筆記連結,可發展成一篇文章或一本書的綱要。</li></ul><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">如下圖:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyLg937Ti_ZHovasaTXv9-uTmByUW4Bp0p4M4fl8f4DA3h1r7FGA6gzUgUBuFBpJumPCXY17X0PrIhlzMe4oLT0VpFLvoJED_IBxMho7YPmrvZ5f3ASl-Wtc6eTJTeli_UrbKlhC1zX7K26HuOYiDThS5VqeRPvclfm9rNWppEvUe-wTThdwx84P8H/s439/obsidian-vault-structure.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="439" data-original-width="428" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyLg937Ti_ZHovasaTXv9-uTmByUW4Bp0p4M4fl8f4DA3h1r7FGA6gzUgUBuFBpJumPCXY17X0PrIhlzMe4oLT0VpFLvoJED_IBxMho7YPmrvZ5f3ASl-Wtc6eTJTeli_UrbKlhC1zX7K26HuOYiDThS5VqeRPvclfm9rNWppEvUe-wTThdwx84P8H/s16000/obsidian-vault-structure.png" /></a></div><br /><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">其中編號 3 至 5 的資料夾,都是用來存放特定主題的<b>永久筆記</b>。另一種安排是建立一個「永久筆記」資料夾,然後在這個資料夾底下再建立其他主題分類的資料夾。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">「X-主題索引」資料夾是專門用來整理特定主題的相關筆記連結。每一個索引類型的筆記,都有潛力變身為一本書籍或 Youtube 影片的完整綱要。對我來說,這是花時間整理筆記所能帶來的巨大好處之一。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><blockquote style="background-color: white; border-left: 0.25em solid var(--color-border-default); box-sizing: border-box; color: var(--color-fg-muted); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin: 0px 0px 16px; padding: 0px 1em;"><p dir="auto" style="box-sizing: border-box; margin-bottom: 0px; margin-top: 0px;">Vault 實際上是一個資料夾,而每一篇筆記無論長短,都是一個副檔名為 .md 的 Markdown 檔案。</p></blockquote><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">接著說說我在組織資料夾和檔案時碰到的疑問和選擇。</p><h3 dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px;"><a aria-hidden="true" class="anchor" href="https://github.com/huanlin/allnotes/blob/main/Mastermind/5-%E7%94%9F%E7%94%A2%E5%8A%9B/Obsidian/%E6%88%91%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%20Obsidian%20%E4%BE%86%E5%B9%AB%E5%8A%A9%E5%AD%B8%E7%BF%92%E8%88%87%E5%AF%AB%E4%BD%9C.md#%E8%A6%81%E4%B8%8D%E8%A6%81%E5%BB%BA%E7%AB%8B%E5%A4%9A%E5%80%8B%E7%AD%86%E8%A8%98%E5%BA%AB" id="user-content-要不要建立多個筆記庫" style="background-color: transparent; box-sizing: border-box; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a>要不要建立多個筆記庫?</h3><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">把所有筆記放在同一個 vault 的好處是可以很容易為它們之間建立雙向連結。我們可以把一個 vault 想像成一間圖書館,館中的書籍很容易互相參照連結。每一個 Obsidian 應用程式視窗只能開啟一個 vault,如果要同時開啟兩個 vaults,就會有兩個 Obsidian 視窗。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">建議:初次接觸 Obsidian 時,先別想太多,用一個 vault 就好,等到發現真的需要把筆記進一步隔離,再來搬移也不遲(主要是檔案複製或搬移的操作而已)。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">一種可能需要建立多個 vaults 的情況:需要明確區分私人與工作的筆記。</p><h3 dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px;"><a aria-hidden="true" class="anchor" href="https://github.com/huanlin/allnotes/blob/main/Mastermind/5-%E7%94%9F%E7%94%A2%E5%8A%9B/Obsidian/%E6%88%91%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%20Obsidian%20%E4%BE%86%E5%B9%AB%E5%8A%A9%E5%AD%B8%E7%BF%92%E8%88%87%E5%AF%AB%E4%BD%9C.md#%E8%A6%81%E4%B8%8D%E8%A6%81%E5%BB%BA%E7%AB%8B%E6%9B%B4%E8%A9%B3%E7%B4%B0%E7%9A%84%E5%88%86%E9%A1%9E%E8%B3%87%E6%96%99%E5%A4%BE" id="user-content-要不要建立更詳細的分類資料夾" style="background-color: transparent; box-sizing: border-box; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a>要不要建立更詳細的分類資料夾?</h3><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">按<a href="https://www.huanlintalk.com/2023/02/how-to-take-smart-notes.html" target="_blank">卡片盒筆記</a>的理念,「分類」不是頂重要急迫的事。一般建議是不要擔心如何建立明確的分類,而應該先專注於隨時記錄,並透過雙向連結來逐漸形成知識結構。等到自然形成一些結構之後再進行分類。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">因為一旦開始想要分類得明確,就容易陷入困擾:「這篇筆記到底要放在哪個分類呢?雖然是程式設計的筆記,可是裡面也有提到網路知識和 AI,我該放哪個資料夾才合適?」如果經常需要煩惱這類問題,不僅沒有幫助,還會耗損意志力,降低學習與生產效率。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">我選擇建立一層作為大分類的資料夾,例如「閃念筆記」、「資訊技術」。在這個資料夾底下,則暫且不建立更細的分類資料夾,等到將來確定有需要進一步分類管理時再去細切。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><blockquote style="background-color: white; border-left: 0.25em solid var(--color-border-default); box-sizing: border-box; color: var(--color-fg-muted); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin: 0px 0px 16px; padding: 0px 1em;"><p dir="auto" style="box-sizing: border-box; margin-bottom: 0px; margin-top: 0px;"><b>訣竅</b>:透過雙向連結來自然形成筆記之間的關聯與結構,並可搭配 tags(標籤)來方便組織與尋找特定主題的筆記。</p></blockquote><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">有些人喜歡把筆記庫組織成「第二個腦袋」,盡力蒐集日常所見、所思,並加以分門別類,於是逐漸形成類似底下這樣比較多層的結構:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-0WidqiYjhyE_Mn_SLekq0DS36w8lMRJ_lEeWlEPNgFRc3hBgWrbGuY1eMz8lpf80hhnQTnc_OMIQhHycVxHo-W5Ff2x_zOyDrYX9jc5A6g496U8cRtGquZWQGBKdNTHctyTvQQ9fcKiFKcf9tNbfaqIBCdODVSXQ_vxpB0_Wcd6ng_F_nLJkeVj0/s539/obsidian-as-2nd-brain.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="539" data-original-width="268" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-0WidqiYjhyE_Mn_SLekq0DS36w8lMRJ_lEeWlEPNgFRc3hBgWrbGuY1eMz8lpf80hhnQTnc_OMIQhHycVxHo-W5Ff2x_zOyDrYX9jc5A6g496U8cRtGquZWQGBKdNTHctyTvQQ9fcKiFKcf9tNbfaqIBCdODVSXQ_vxpB0_Wcd6ng_F_nLJkeVj0/s16000/obsidian-as-2nd-brain.png" /></a></div><br /><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><h3 dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: 16px; margin-top: 24px;"><a aria-hidden="true" class="anchor" href="https://github.com/huanlin/allnotes/blob/main/Mastermind/5-%E7%94%9F%E7%94%A2%E5%8A%9B/Obsidian/%E6%88%91%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%20Obsidian%20%E4%BE%86%E5%B9%AB%E5%8A%A9%E5%AD%B8%E7%BF%92%E8%88%87%E5%AF%AB%E4%BD%9C.md#%E9%9D%9E%E6%96%87%E5%AD%97%E9%A1%9E%E5%9E%8B%E7%9A%84%E6%AA%94%E6%A1%88%E8%A6%81%E5%8F%A6%E9%97%A2%E8%B3%87%E6%96%99%E5%A4%BE%E5%AD%98%E6%94%BE%E5%97%8E" id="user-content-非文字類型的檔案要另闢資料夾存放嗎" style="background-color: transparent; box-sizing: border-box; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a>非文字類型的檔案要另闢資料夾存放嗎?</h3><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">筆記中如果會用到其他非文字類型的檔案(例如圖片),是要跟筆記放在同一個資料夾,還是要另闢資料夾存放?這是我在剛開始用 Obsidian 的時候碰到的疑問。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">這些與筆記關聯的非文字的檔案,在 Obsidian 中統稱為「<span style="box-sizing: border-box; font-weight: var(--base-text-weight-semibold, 600);">附件</span>」(attachments)。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">預設情況下,Obsidian 會把筆記所參考的附件放在 vault 的根目錄下,而<b>我的習慣是把附件放在文章所在位置的子目錄下</b>,這個子目錄通常命名為 "attachments"<b>。</b>你可以參考稍後影片中的 Obsidian 偏好設定來更改附件資料夾的名稱與位置。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><blockquote style="background-color: white; border-left: 0.25em solid var(--color-border-default); box-sizing: border-box; color: var(--color-fg-muted); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin: 0px 0px 16px; padding: 0px 1em;"><p dir="auto" style="box-sizing: border-box; margin-bottom: 0px; margin-top: 0px;"><b>要不要按類型區分附件資料夾?</b> 在 Obsidian 中寫筆記的時候,我認為「快速寫下來」比「條理分明的分類階層」更重要,故分類階層應該盡量簡化。看一下 Obsidian 的選項設定,也能發現它只讓你指定一個附件資料夾的名稱。因此,如果連附件也按照類型來個別建立 images、audios、videos 等資料夾,那對我來說就過頭了,容易衍生不必要的麻煩。</p></blockquote><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><h2 style="text-align: left;">小技巧:在筆記中快速插入圖片或附件</h2><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">我經常是以下列兩種方式來插入筆記中的圖片或附件(例如 PDF 檔案):</p><ul style="text-align: left;"><li><span face="-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"" style="background-color: white; color: #24292f; font-size: 16px;">方法一:在檔案總管或任何支援檔案拖曳的應用程式中,以滑鼠拖曳的方式把檔案直接拉到 Obsidian 視窗的編輯區。</span></li><li><span face="-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"" style="background-color: white; color: #24292f; font-size: 16px;">方法二:先把圖片複製到剪貼簿,然後</span>到 Obsidian 視窗的編輯區中按 Ctrl+V 貼上。</li></ul><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">使用以上任何一種方法,Obsidian 都會把那個檔案<b>複製一份到附件的預設資料夾</b>,並於筆記中產生該檔案的連結。如此一來,我不用輸入任何 Markdown 標記就能完成插入圖片的操作。</p><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;">值得一提的是,如果使用方法二(複製圖片然後貼上),Obsidian 會在附件資料夾中產生一個檔案,檔名可能會有「Pasted image」字樣,後面跟著一串日期時間戳記(西元年月日、時分秒),例如「 Pasted image 20230204202859.png」。所以每次貼上圖片之後,我會在編輯區的圖片連結上面點一下滑鼠右鍵來重新命名檔案。操作如下圖:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOUA8kbKppjYRmi19RumoUwBW-UylNlnnKsVJ_MMKi7r2a-DGNbSb1SyrHuO-hsoCduMlo11OCgMDzy1aXrZSWoZ2L99eSkk9TosZKxG3mnM-VQwxI-2xCPi3sfgjoIwkfDX3QMT64Ekma9A3KDc9eOvJJ-TwVVWsKpvNjfpYxWaKzpmrHzdQiVbDb/s1292/20230315-obsidian-copy-paste-image.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="812" data-original-width="1292" height="402" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOUA8kbKppjYRmi19RumoUwBW-UylNlnnKsVJ_MMKi7r2a-DGNbSb1SyrHuO-hsoCduMlo11OCgMDzy1aXrZSWoZ2L99eSkk9TosZKxG3mnM-VQwxI-2xCPi3sfgjoIwkfDX3QMT64Ekma9A3KDc9eOvJJ-TwVVWsKpvNjfpYxWaKzpmrHzdQiVbDb/w640-h402/20230315-obsidian-copy-paste-image.gif" width="640" /></a></div><br /><p dir="auto" style="background-color: white; box-sizing: border-box; color: #24292f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; margin-bottom: 16px; margin-top: 0px;"><br /></p><h2 style="text-align: left;">我的 Youtube 影片</h2><div>我原本打算錄製一個影片來介紹卡片盒筆記和 Obsidian 的入門知識,結果影片做得太長,最終拆成兩個影片分開上傳至 Youtube,連結如下:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="356" src="https://www.youtube.com/embed/X2EnNG3Encg" width="481" youtube-src-id="X2EnNG3Encg"></iframe></div><div><br /></div><br /><br /><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="361" src="https://www.youtube.com/embed/8OP5Hy1u3wA" width="482" youtube-src-id="8OP5Hy1u3wA"></iframe></div><br /><div><br /></div><div>我的影片的風格應該是比較枯燥,有待改進。😌</div><div><br /></div><div>網路上也有許多 Obsidian 文章和教學影片可以參考。先這樣吧!</div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-21495809418716338322023-02-12T17:09:00.018+08:002023-04-15T10:46:53.573+08:00《卡片盒筆記》閱讀心得<p>這是我閱讀《卡片盒筆記》這本書所寫的筆記,並分享我在寫筆記這方面的一點實務技巧與心得。</p><span><a name='more'></a></span><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjW14OOwPAv0a-o2AF7vJiff4ZyIAafBKjkReEcaQMxzDVfxQb6AP9Ouje7nnZ0Ym3mfqIuGXWbXJsJxGWxxpHbdPLZeJ1WhCDCMrhO4pbvflgaftrPoSxyfKK3QkB17mWzUb1s0kTUDCAUL2kr9ACt9zodFTPcTMgPZF6Y_0xPbQj1x0crSOI_-_ZH/s395/zoom_big_138998.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="395" data-original-width="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjW14OOwPAv0a-o2AF7vJiff4ZyIAafBKjkReEcaQMxzDVfxQb6AP9Ouje7nnZ0Ym3mfqIuGXWbXJsJxGWxxpHbdPLZeJ1WhCDCMrhO4pbvflgaftrPoSxyfKK3QkB17mWzUb1s0kTUDCAUL2kr9ACt9zodFTPcTMgPZF6Y_0xPbQj1x0crSOI_-_ZH/s16000/zoom_big_138998.jpg" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><p></p><ul style="text-align: left;"><li>書名:卡片盒筆記</li><li>作者:Sonke Ahrens</li><li>譯者: 吳琪仁</li><li>出版:遠流 (2022 年 4 月)</li></ul><p></p>
<hr />
<div style="text-align: left;"><br /></div><div style="text-align: left;">你是否碰過以下問題:</div><div style="text-align: left;"><ul style="text-align: left;"><li>讀書筆記摘抄了一大堆,隔沒多久都忘了自己讀過哪些書,做過哪些筆記。</li><li>想要好好針對一個主題寫文章,但總是寫了一點就卡住,想不出該寫什麼。</li><li>臨時想到的點子不知道怎麼有效的紀錄下來,為日後的自己服務。</li></ul><div><br /></div><div>不妨試試卡片盒筆記法。</div></div><h2 data-heading="筆記類型">筆記類型</h2><p>卡片盒筆記的寫作方法涉及四種類型的筆記:</p><ol><li><b>Fleeting notes</b>:靈感筆記,也有人翻譯成「閃念筆記」或「臨時筆記」。<br />p.s. 我個人更常使用「閃念筆記」,除了先入為主,我也覺得「閃念」一詞頗到位。</li><li><b>Literature notes</b>:文獻筆記、閱讀筆記。只要是從他人的文章、書籍、影音等作品中發現值得紀錄的東西,都可以摘錄下來,並加入自己的理解與想法,整理成文獻筆記。</li><li><b>Permanent notes</b>:永久筆記,內容通常是閃念筆記和閱讀筆記的整合與延伸,有更多自己的創見與想法,值得永久保存。</li><li><b>Index notes</b>:索引筆記,內容主要是與特定主題有關的筆記連結,用途是組織特定主題。每一條索引筆記就像是特定主題的入口,同時也方便未來進一步整理成文章或書籍。</li></ol><div><br /></div>
<blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div><div style="text-align: left;"><svg class="svg-icon lucide-pencil" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><line x1="18" x2="22" y1="2" y2="6"></line><path d="M7.5 20.5 19 9l-4-4L3.5 16.5 2 22z"></path><marker id="mermaid_arrowhead" markerheight="6" markerunits="strokeWidth" markerwidth="8" orient="auto" refx="9" refy="5" viewbox="0 0 10 10"><path class="arrowheadPath" d="M 0 0 L 10 5 L 0 10 z" style="stroke-dasharray: 1, 0; stroke-width: 1;"></path></marker></svg><b>閃念筆記和文獻筆記的保存期限</b> </div></div></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div><div style="text-align: left;">一旦某則閃念筆記擴展成永久筆記,便可將它刪除。至於文獻筆記要不要刪除,就看個人偏好而定了。即使我的閱讀筆記擴展成為永久筆記,我通常傾向保留它們。</div></div></blockquote>
<p><br /></p><p>實務上,我是以資料夾來區分上述四種類型的筆記,如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh22NMcGLm6PZ5FoRP-HyvZcHVI9WLx0NtjYNCjxv8slhnEk0LYi6lRHAzx-JNo-ILPsl0iTsFkhx0cHTm5oshJRcaWP0Ff-uO6R2sNdRZb5i4Gaw5R3rSl2EIdkTITb_cp_x9SCJSXnWnGxhlEegbqJyitaKQdhaXqJeHkj9zbNxy_8Qxu39pukbji/s341/obsidian-folders.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="341" data-original-width="312" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh22NMcGLm6PZ5FoRP-HyvZcHVI9WLx0NtjYNCjxv8slhnEk0LYi6lRHAzx-JNo-ILPsl0iTsFkhx0cHTm5oshJRcaWP0Ff-uO6R2sNdRZb5i4Gaw5R3rSl2EIdkTITb_cp_x9SCJSXnWnGxhlEegbqJyitaKQdhaXqJeHkj9zbNxy_8Qxu39pukbji/s320/obsidian-folders.png" width="293" /></a></div><br /><p>我使用的筆記軟體是 <a href="https://obsidian.md/" target="_blank">Obsidian</a>,而上圖就是擷取自我的 Obsidian 工作環境。圖中紅色方框的部分,由上至下分別對應至剛才說的三種筆記:<b>閃念筆記</b>、<b>文獻筆記</b>、<b>索引筆記</b>。資料夾前面的數字或英文字母只是為了按照我的意思來排序。其中的「0-日誌」裡面會存放日常筆記,且每一則筆記的檔案名稱都以日期來命名,例如 "2023-02-12",方便日後以時間順序來查看過去曾經紀錄哪些點子或重要事項。</p><p><br /></p><p>我沒有建立「永久筆記」資料夾,而是把各主題的永久筆記直接建立成根目錄下的一級資料夾,例如圖中的「3-dotnet」就是用來存放與 .NET 程式設計有關的永久筆記,該資料夾底下則又可以再細分不同主題的資料夾,例如 Blazor、CSharp。</p><blockquote><p>為什麼資料夾「CSharp」不用更短的「C#」來命名?這是因為 Obsidian 不允許以 "#" 作為檔案和資料夾名稱。</p></blockquote><p>至於最底下的「Z-樣板」,則是用來存放常用的內容樣板。這是 Obsidian 軟體的功能,這裡就不特別展開說明了。</p><h2 data-heading="筆記類型">寫作流程</h2><p>前述四種類型的筆記也和寫作流程有關,具體步驟如下:</p><ol><li>隨時將捕捉到的靈感快速寫下來,放入<b>閃念筆記</b>。</li><li>閱讀任何文章或書籍時,整理讀書筆記,放入<b>文獻筆記</b>。</li><li>閱讀閃念筆記和文獻筆記,看看有沒有什麼想法值得進一步整理成<b>永久筆記</b>。</li><li>把新寫好的永久筆記添加到卡片盒。此步驟如果以筆記軟體 Obsidian 的功能來說,我認為就是建立筆記之間的<b>雙向連結</b>,以及建立<b>索引筆記</b>。</li><li>嘗試從既有的筆記由下而上發展成更大的寫作主題。實務上,在 Obsidian 中,每一個正在發展茁壯的主題可以是一個包含許多筆記連結的「索引筆記」,或許稱它為「主題索引」更加貼切。</li><li>基於<b>索引筆記</b>的內容來建立文章或書籍的初稿。此時我會改用專門用來出版的編輯與排版軟體,主要的工作則是參照<b>索引筆記</b>的內容,把其中的每一篇筆記逐一複製到排版軟體,形成初稿。</li><li>在排版軟體中反覆編輯、校對書稿,直到完成、出版。休息並獎勵自己一番,然後繼續處理其他尚未完成的主題。</li></ol><p>以上並非原文照錄,而是參考原書〈第一章:緒論〉中的〈撰寫論文的步驟〉,根據我的理解以及實務經驗所設想的寫作流程。其中的第 5 和第 6 步驟與原書差異較大,尤其是「主題索引」,原書並沒有這個詞彙。</p><h2 data-heading="三種筆記的要點">三種筆記的要點</h2><p>接著從書中摘錄三種筆記的重要觀念與實施要點。</p><h3 data-heading="閃念筆記">閃念筆記</h3><ul><li>⼿邊需要隨時有記筆記的⼯具,以捕捉腦海中閃現的每⼀個想法。不必多慮如何寫或者寫什麼。這些都是閃念筆記,僅僅是你腦海中想法的備忘錄,不⽤想太多其他東⻄。</li><li>如果你已經整理好⾃⼰的想法,⽽且時間比較寬裕,也可以跳過這⼀步,直接把想法寫下來,作為⼀條已完成的永久筆記放在你的卡片盒裡。</li></ul><h3 data-heading="文獻筆記">文獻筆記</h3><ul><li>無論你讀什麼,都要做筆記,寫下你不想忘記的內容,或者你認為可能會在⾃⼰的思考或寫作中使⽤的內容。</li><li>文獻筆記要非常簡短,精⼼選擇,並使⽤⾃⼰語⾔記錄,對引文要格外挑剔,不要只是抄寫,⽽不去真正理解其含義。最後把這些筆記和參考書⽬的細節⼀起保存在你的文獻管理系統中。</li></ul><p>以下摘自〈第三章:成功寫作的六個步驟〉。</p><ul><li>文獻筆記是我們理解和把握文本的⼯具,如果文本比較難,就需要把文獻筆記做得詳盡⼀些;如果文本比較簡單,只需要記下⼀些關鍵詞就可以了。</li><li>每當我們探索⼀個新的、陌⽣的主題時,我們的筆記往往會比平常更多,不必為此⽽緊張,因為這是對理解能⼒的刻意練習,是⼀個不可或缺的環節。有時候,需要慢慢地去理解⼀篇難懂的文章,⽽有時候則可以把整本書的內容縮減為⼀句話。<strong>筆記的多與少和理解的快與慢並不是最重要的,重要的是這些筆記能夠為下⼀步寫永久筆記提供儘可能⼤的幫助。</strong></li><li>如果記筆記時沒有明確的⽬的,就很難意識到它在⼀個大專案中的重要意義,就會感覺記筆記更像是⼀件苦差事。……在卡片盒系統中,⼀切都是為了在卡片盒中積累⾜夠數量的有⽤筆記,這給我們的閱讀和做文獻筆記指明了清晰的⽅向。</li><li>你需要把對文本的理解做成⼀定形式的文獻筆記,這樣在做永久筆記時才有具體的素材。但不要把記文獻筆記本⾝變成⼀個⼤⼯程。文獻筆記應當簡短⽽有助於寫永久筆記,除此以外,其他事情要麼可以幫助達到這⼀⽬的,否則就是對注意⼒的分散。</li><li>你可以⽤ Zotero 記錄文獻筆記,這樣筆記和書⽬細節能⼀起保存起來。</li><li>簡要描述文本的主要觀點,⽽不只是蒐集引文。</li><li>做文獻筆記是⼀種刻意練習,因為我們可以通過反饋知道⾃⼰是否理解了它們,⽽努⼒⽤我們⾃⼰的語⾔來表達⼀件事的要點,也是理解我們所讀內容的最好⽅法。</li></ul><h3 data-heading="永久筆記">永久筆記</h3><ul><li>我們做永久筆記,與其說是⼀種預先構思好的⽅案,不如說是在寫作過程中進⾏的思考,是與卡片盒內已有筆記的對話。</li></ul><h2 data-heading="寫作流程的相關要點">寫作流程的相關要點</h2><p>以下摘自〈第三章:成功寫作的六個步驟〉。</p><ul><li>敞開⼼扉是尋求洞⾒的第⼀步,也是最重要的⼀步,就是將我們發現論點和想法的⼯作⽅式從由上⽽下變為由下⽽上。</li><li>先集中精⼒於讓卡片盒實現群聚效應的積累,⽽不要立刻決定具體要寫什麼,也不要⼀直想著⾃⼰的預設立場。為此,我們應該做到以下幾點:<br />
<ul>
<li>確認是否已將任務分解,並專注於理解我們所閱讀的文本;</li>
<li>確保我們已經準確地為所閱讀的內容做好筆記;
</li><li>找到筆記之間的關聯性,並建立聯繫。</li>
</ul><br />
然後,我們才抽⾝出來看⼀看我們已經做的這些⼯作,再去確定我們要從中得出什麼結論。
</li><li>對於真正的、有⽤的學習來說,真正有幫助的是將⼀條訊息與盡量多的、有意義的上下文聯繫起來,就如我們將卡片盒中的筆記相互聯繫起來的做法那樣。刻意建立這些聯繫,意味著建立了⼀個由相互聯繫的想法和事實組成的⾃我⽀持的網路,這些思想和事實互為線索,相互提⽰。</li><li><strong>卡片盒沒有按主題分類,這是積極建立筆記之間聯繫的前提條件。</strong> 只要聯繫是有意義的,不同的筆記之間也可以建立聯繫。</li><li>鬆散的順序允許在必要時⾃由地改變筆記的主題⽅向,並能提供⾜夠的結構來建立複雜性。筆記的價值只取決於它所嵌入的筆記和參考網絡。</li><li>卡片盒的⽬的不是要成為⼀部百科全書,⽽只是⽤於思考的⼯具,所以我們不需要擔⼼它當前是否完整。只有在它對我們⾃⼰的思考有幫助時才需要寫;⽽不必為了彌補筆記序列中的空⽩⽽寫下什麼。</li></ul><div><br /></div><h2 style="text-align: left;">結語</h2><div>我認為書中提倡「由下而上」來自然形成自己的知識架構是很棒的方法,這可以讓我們隨時記下突然迸出來的靈感,而不用被更大的寫作目標擋住去路,或者因為完美主義而阻撓不前。寫就對了!</div><div><br /></div><div>就如書中所說:「如果我們⼀開始就決定使⽤卡片盒,把寫作當成我們整個智⼒活動的⼿段和⽬標,那麼尋找寫作主題就完全不是件難事了。此時問題的關鍵不再是找到⼀個主題來寫,⽽是通過寫作來發展我們找到的主題。」</div><div><br /></div><div>另一方面,卡片盒筆記法「沒有按主題分類」,這點也值得留意。實務應用上,在使用筆記軟體時,可能很難克制自己去建立個別主題的分類資料夾——我自己就是這樣。不過有些時候,則是某個主題在我心中已經明確知道大致有哪些細部拆分的議題,此時我還是會選擇由上而下的方式來組織筆記。對於日常讀書或者上網時發現需要記下的片段或靈感,自然就是按照卡片盒筆記所建議的方法,先收進<b>閃念筆記</b>或<b>文獻筆記</b>,待日後內容逐漸成熟茁壯,再移動至<b>永久筆記</b>。</div><div><br /></div><div>最後想說的是,無論分類做得多細,筆記排版得多美觀,能否給自己或他人提供價值才是重點。我也常提醒自己,作筆記不能光是剪貼一堆原作內容,而應該把輸入轉換成大約等量的輸出,才算是有效的學習。</div><div><br /></div><div>Happy writing!</div><div><br /></div><div><b><span style="color: #cc0000;">後記</span>:</b>我接著整理了 Obsidian 筆記軟體的使用心得,並錄製了影片。詳見〈<a href="https://www.huanlintalk.com/2023/03/obsidian-beginners-note.html" target="_blank">Obsidian 初學者筆記</a>〉。</div>
<p></p>
<p></p>
Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-21454649367924403152022-12-26T13:54:00.002+08:002022-12-26T14:00:15.463+08:00MAUI app 無法在 Windows 10 22H2 環境上執行 (0x80070005, 0x80040154)<p> 記錄一個狀況:MAUI app 無法在 Windows 10 22H2 環境上執行。</p><span><a name='more'></a></span><p><br /></p><h2 style="text-align: left;">問題描述</h2><p>同一個 MAUI 專案,在 Windows 11 機器上執行沒問題,但是無法在 Windows 10 機器上執行。</p><p><br /></p><div style="text-align: left;"><b>開發環境:</b></div><p></p><ul style="text-align: left;"><li>兩台機器上面的 Visual Studio 版本相同,都是 VS 2022 17.5.0 Preview 2.0。</li><li>Windows 10 版本是 22H2 build 19045.2364。</li><li>MAUI 專案採用了 Blazor Hybrid 技術,目標平台為 .NET 7。</li></ul><p></p><p><br /></p><div style="text-align: left;"><b>狀況:</b></div><p>在 Windows 10 機器上,使用 Visual Studio 2022 開啟我的 MAUI 專案之後,按 F5 或 Ctrl+F5 都沒反應,而 Output 視窗裡面有錯誤訊息:</p><p><br /></p><p><span style="color: #cc0000; font-family: Consolas;">The program 'MyMauiApp.exe' has exited with code 2147942405 (0x80070005).</span></p><p><br /></p><p>此狀況與 <a href="https://github.com/dotnet/maui/issues/12080" target="_blank">MAUI 問題單 #10280</a> 的描述很像。此外,我在 Windows 事件檢視器裡面也有發現兩筆相關的錯誤訊息。一個是 Application Error:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMLG97pHpM18IbuI48yPA2w9t7UxNwgq6elTZ2LdDSfjiE6djR7gUs8rI77aHz96yzfkcL4eMs0LaC0EYD0sHF8acc1IRFY4i4sTdEQ2u6ctslsTcaYbzYfDR-uSChf6-TjvSi0vCbOWaNwXbnQnDLJUTxWhKXNfK1TsZl9-Z3QLlM0JxuTJ_Yd-qf/s1080/maui-app-error1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="532" data-original-width="1080" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMLG97pHpM18IbuI48yPA2w9t7UxNwgq6elTZ2LdDSfjiE6djR7gUs8rI77aHz96yzfkcL4eMs0LaC0EYD0sHF8acc1IRFY4i4sTdEQ2u6ctslsTcaYbzYfDR-uSChf6-TjvSi0vCbOWaNwXbnQnDLJUTxWhKXNfK1TsZl9-Z3QLlM0JxuTJ_Yd-qf/w640-h316/maui-app-error1.png" width="640" /></a></div><br /><p>事件檢視器中的另一個錯誤是來自 .NET Runtime:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgokR8lCYHoAFythYfdh2ER-Ny-yzSSxwR86C59-bOs2x8KBEX84-i0xTQW7t4sAfiCWKKliETSSedjjFjgqyTSEffIO_v-Ssx4seVIAO6GWjwS5HGn67f55loUQftOWgJih2PhzYZB2RuMRqtFpQ7e10g77u2G-Bc0KilaXJyjyWWg7tzoyD_jJtJh/s1252/2022-12-26_13-33-10.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="655" data-original-width="1252" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgokR8lCYHoAFythYfdh2ER-Ny-yzSSxwR86C59-bOs2x8KBEX84-i0xTQW7t4sAfiCWKKliETSSedjjFjgqyTSEffIO_v-Ssx4seVIAO6GWjwS5HGn67f55loUQftOWgJih2PhzYZB2RuMRqtFpQ7e10g77u2G-Bc0KilaXJyjyWWg7tzoyD_jJtJh/w640-h334/2022-12-26_13-33-10.png" width="640" /></a></div><br /><p>這個 .NET runtime 錯誤是一個 COM Interop exception,錯誤代碼為 0x80040154。</p><p><br /></p><p>我參考 <a href="https://github.com/dotnet/maui/issues/12080" target="_blank">MAUI 問題單 #10280</a> 下方留言的一些建議,能試的方法都試一下。然後,問題似乎解決了。說「似乎」,是因為我覺得目前無法百分之百確定此問題不會再出現,需要再多觀察一下。</p><p><br /></p><h2 style="text-align: left;">解決過程</h2><p>在我的 Windows 10 機器上安裝 <a href="https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/downloads#windows-app-sdk-12" target="_blank">Windows App SDK 1.2.2</a>,重開機。</p><p><br /></p><p>執行上述操作後,用 Visual Studio 2022 開啟我的 MAUI 專案,然後直接按 Ctrl+F5,依然沒有任何動靜。從工作管理員可以看到 Visual Studio 正在忙著工作。</p><p><br /></p><p>等待幾分鐘,Visual Studio 依然沒有反應,於是我重新啟動 Visual Studio,使用 MAUI 專案範本來建立一個最簡單的 MAUI 專案。結果這個用預設專案範本所建立程式也一樣無法執行。接著,有趣的事情發生了。</p><p><br /></p><p>我再度重啟 Visual Studio,開啟剛才建立的 MAUI 專案,竟然可以順利執行了!於是我開啟原先那個無法執行的 MAUI 專案(有用到 Blazor Hybrid 技術的那個),在沒有修改任何程式碼的情況下,卻也可以順利執行了。</p><p><br /></p><p>雖然現在看似問題已經解決,但總覺得過程有點蹊蹺,無法百分百確定問題不會再發生。或許在我東試西試的過程中,有某個關鍵步驟是我沒有留意到的。</p><p><br /></p><p>先記錄下來,如果相同狀況不再出現,那便是真的解決了。</p><p><br /></p><p>Keep coding!</p>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-6629403372514435962022-12-22T13:06:00.036+08:002022-12-24T16:13:49.858+08:00MudBlazor 快速上手<p>算是 Blazor 入門筆記的第 4 篇吧。🙂</p><span><a name='more'></a></span><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSvMhC0AM03D2DuVwqsdSBO9b8xGX3sg4DINbKtwLWSsES4ttTQlVJLXcc3ECUrbuLnRmbD0YEU3fguC1oxnHhVcQoquusYQdBzbAb7Xfzf6kDbSzdtLqNRm_OQ6_avV7-1mvvJSnsw1M0980_aFziQUmYbQjFiz2wMUW1A4HA-oCitihuxJmSvkN7/s617/article-banner.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="452" data-original-width="617" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSvMhC0AM03D2DuVwqsdSBO9b8xGX3sg4DINbKtwLWSsES4ttTQlVJLXcc3ECUrbuLnRmbD0YEU3fguC1oxnHhVcQoquusYQdBzbAb7Xfzf6kDbSzdtLqNRm_OQ6_avV7-1mvvJSnsw1M0980_aFziQUmYbQjFiz2wMUW1A4HA-oCitihuxJmSvkN7/s16000/article-banner.png" /></a></div><div><br /></div><h2 style="text-align: left;">如何使用這個筆記</h2><div>按目前的想法,我的 Blazor 筆記頂多只會觸及一些基本功。如果你正打算開始學習 Blazor,這些筆記或許有點幫助。我建議碰到需要動手寫程式的時候就照著範例寫一遍;如果發現我沒寫清楚的地方,就查 google 或閱讀 learn.microsoft.com 的<a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/" target="_blank">技術文件</a>與<a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/tutorials/" target="_blank">教學課程</a>。</div><div><br /></div><div>底下是本系列各篇筆記的連結:</div><div><ol style="text-align: left;"><li><a href="https://www.huanlintalk.com/2022/12/net-blazor-introduction.html" target="_blank">簡介</a></li><li><a href="https://www.huanlintalk.com/2022/12/net-blazor-your-first-app-with-custom.html" target="_blank">拆解專案結構、自訂版面配置</a></li><li><a href="https://www.huanlintalk.com/2022/12/deploying-blazor-app-to-github-pages.html" target="_blank">部署 Blazor WebAssembly app 至 GitHub Pages</a></li><li>MudBlazor 快速上手(你目前所在位置)</li></ol></div><div><br /></div><div>相關參考資料已經寫在第一集〈<a href="https://www.huanlintalk.com/2022/12/net-blazor-introduction.html" target="_blank">.NET Blazor 筆記:簡介</a>〉,這裡就不再重複。</div><div><br /></div><div><br /></div>
<hr />
<p><b>內容大綱:</b></p><p></p><ul style="text-align: left;"><li>前言</li><li>MudBlazor 簡介</li><li>使用 MudBlazor 前後的差異</li><li>實作步驟</li><li>下一步</li><li>結語</li></ul><p></p>
<hr />
<p><br /></p><h2 style="text-align: left;">前言</h2><p>前面整理了三篇 Blazor 入門筆記,除了基礎觀念之外,程式實作的部分都是採用 standalone WASM 模式,也就是單獨運行於前端瀏覽器,沒有倚賴任何後端 ASP.NET Core 服務。往後的文章(如果有的話),預計會逐漸偏向 hosted Blazor WASM 以及 Blazor Server,也就是後端有 ASP.NET Core 的架構。在此之前,我想稍微輕鬆一下,玩一點 UI 元件庫的東西。</p><p><br /></p><p>粗淺調查目前眾多開源 Blazor 元件庫,我比較屬意的是 <a href="https://mudblazor.com/" target="_blank">MudBlazor</a> 和 <a href="https://blazorise.com/" target="_blank">Blazorise</a>,而且據說這兩個元件庫可並用於同一個專案中(可能需要調整一些 CSS)。</p><p><br /></p><p>這篇筆記不會去評比各家 Blazor 元件庫的優缺點,畢竟我對前端技術不熟,單純是想要找一個簡單好上手、功能夠我用、文件完整的元件庫,幫我加強 UI 的部分,並提高一點生產力。</p><blockquote><p><a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/components/built-in-components" target="_blank">點我查看 Blazor 內建的元件有哪些</a></p></blockquote><p><br /></p><p><b>開發環境</b>:Windows 11、.NET 7 SDK、Visual Studio 2022 version 17.5.0。</p><p><br /></p><h2 style="text-align: left;">MudBlazor 簡介</h2><p>MudBlazor 特色如下:</p><p></p><ul style="text-align: left;"><li>基於 Google 提出的<b>質感設計</b>(<a href="https://m3.material.io/" target="_blank">Material Design</a>)。</li><li>清晰易讀的說明文件和範例。</li><li>所有元件皆以 C# 寫成,只有在絕對必要的情況才會使用 JavaScript。</li><li>不必是 CSS 專家就能做出美觀的 UI(熟悉 CSS 當然更好)。</li><li>沒有依賴任何其他元件庫,開發小組 100% 掌控元件庫的發展。</li><li>對測試覆蓋率的要求高。(使用 <a href="https://bunit.dev/" target="_blank">bUnit</a> 來撰寫 Blazor 元件的單元測試)</li><li>經常發布新版本。</li></ul><br /><p></p><p>對我這個不熟悉前端技術的人來說,MudBlazor 相當有吸引力。其實不只 MudBlazor,目前可以找到許多開源的 Blazor 元件庫,也都具備了快速打造美觀 UI 的優點,例如 <a href="https://www.radzen.com/" target="_blank">Radzen</a>、<a href="https://blazorise.com/" target="_blank">Blazorise</a>、<a href="https://antblazor.com/" target="_blank">Ant Design Blazor</a>……等等。這些 UI 元件庫似乎可以粗略分為兩個陣營,一個是 <a href="https://getbootstrap.com/" target="_blank">Bootstrap</a>,另一個是 <a href="https://m3.material.io/" target="_blank">Material Design</a>。</p><p><br /></p><p>由於 MudBlazor 屬於 Material Design 這一派,所以我認為最好還是要了解 Material Design 的一些基本觀念和術語,實際寫程式的時候會更加得心應手,知其所以然。</p><blockquote><p>補:後來發現吾友 Vivid 已經寫了數篇 Radzen 文章,<a href="https://blogs.uuu.com.tw/Articles/post/2022/08/24/%E5%9C%A8Blazor-Server%E4%BD%BF%E7%94%A8Radzen%E5%85%83%E4%BB%B6-1.aspx" target="_blank">第一篇</a>和<a href="https://blogs.uuu.com.tw/Articles/post/2022/09/07/%E4%BD%BF%E7%94%A8Radzen%E5%85%83%E4%BB%B6-2.aspx" target="_blank">第二篇</a>是講 data grid ,<a href="https://blogs.uuu.com.tw/Articles/post/2022/09/21/%E5%9C%A8Blazor-Server%E4%BD%BF%E7%94%A8Radzen%E5%85%83%E4%BB%B6-3.aspx" target="_blank">第三篇</a>和<a href="https://blogs.uuu.com.tw/Articles/post/2022/10/05/%E4%BD%BF%E7%94%A8Radzen%E5%85%83%E4%BB%B6-4.aspx" target="_blank">第四篇</a>則陸續介紹了 DataList、DataPager、和其他常用元件的用法。如果早一點看到,搞不好我就用 Radzen 了。(一切都是命😌)</p></blockquote><h2 style="text-align: left;">使用 MudBlazor 前後的差異</h2><p>這裡要展示的是,先以 Blazor WebAssembly App 專案範本來建立一個應用程式,然後在此專案中加入 MudBlazor,替換一些元件,然後觀察應用程式的外觀與操作跟原先預設範本所建立的程式有何差異。</p><p><br /></p><p>有關建立 Blazor WASM app 的操作步驟,可參考此系列筆記的第二篇:<a href="https://www.huanlintalk.com/2022/12/net-blazor-your-first-app-with-custom.html" target="_blank">拆解專案結構、自訂版面配置</a>(有影片)這裡就不重複了。</p><p><br /></p><p>下圖是使用 MudBlazor 之前、也就是 Blazor 預設專案範本所建立應用程式頁面:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir-UY84FrYS6Fn-evpE6_ZjEAzQ1C3nChvXtgtW8YKlMojmDjkVubRzMuJMS_ckWBpwAqNfkfO811XjARQjzsp-vjh79xVqoVl8YzBnYWrcTK4i_k_3zXXxO01nHrMtAtV2gGe0nzf1yprrkmI1rciFLnxA1HEx5N2pN5XfEeiFOTta5-Kpjbu26pT/s1049/before-mudblazor.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="559" data-original-width="1049" height="342" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir-UY84FrYS6Fn-evpE6_ZjEAzQ1C3nChvXtgtW8YKlMojmDjkVubRzMuJMS_ckWBpwAqNfkfO811XjARQjzsp-vjh79xVqoVl8YzBnYWrcTK4i_k_3zXXxO01nHrMtAtV2gGe0nzf1yprrkmI1rciFLnxA1HEx5N2pN5XfEeiFOTta5-Kpjbu26pT/w640-h342/before-mudblazor.png" width="640" /></a></div><br /><p><br /></p><p>加入 MudBlazor 之後,長這個樣子:</p><p><br /></p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6Dl__qBvXjEG_EyUQJ_Rxi2XGumdbSFYDTaXS38rvLVw0Y5lhVwGLM9BWfSP_2XZzDVJm4EkexndQyTFocPmrNFXUfS-W7Hmsf1drzGOPzcJGs9TXR4pgny2tNerTSWVvo2R6bm0o5hu4G4NEct1-zAxOB3MITCrhTR4RTWqysglKA30oQ62jsd35/s1205/after-mudblazor.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="622" data-original-width="1205" height="330" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6Dl__qBvXjEG_EyUQJ_Rxi2XGumdbSFYDTaXS38rvLVw0Y5lhVwGLM9BWfSP_2XZzDVJm4EkexndQyTFocPmrNFXUfS-W7Hmsf1drzGOPzcJGs9TXR4pgny2tNerTSWVvo2R6bm0o5hu4G4NEct1-zAxOB3MITCrhTR4RTWqysglKA30oQ62jsd35/w640-h330/after-mudblazor.png" width="640" /></a></div><br /><p><br /></p><p>看起來,前後差異不大。不過,MudBlazor 能夠做出的效果和品質絕對不只這樣。</p><p><br /></p><p>由上圖可知,我會在範例程式中使用 MudBlazor 的 Data Grid 元件,名稱是 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px; white-space: break-spaces;">MudDataGrid</span>。要提醒的是,此元件目前仍在 beta 版本,所以最好別用在正式產品當中,以免未來元件版本更新時給自己造成一些困擾。</p><p><br /></p><h2 style="text-align: left;">實作步驟</h2><p>以下幾個操作步驟主要是參考 MudBlazor 官方文件:<a href="https://mudblazor.com/getting-started/installation" target="_blank">Getting Started</a>,我只稍微做了一點變化。</p><p><br /></p><p></p><ol style="text-align: left;"><li>安裝 MudBlazor 套件</li><li>加入常用命名空間至 _Imports.razor</li><li>加入字體和樣式連結</li><li>加入 JS</li><li>移除 Bootstrap 與 Open-Iconic(非必要)</li><li>註冊基礎服務</li><li>修改預設的版面配置</li><li>修改左側選單 (NavMenu.razor)</li><li>改用 MudDataGrid 元件(beta)</li></ol><p></p><p><br /></p><p>接著依序說明各個步驟。</p><blockquote><p>如果不想經歷這些修改過程,也可以安裝 MudBlazor 提供的專案範本,直接用它提供的範本來建立專案:<a href="https://github.com/MudBlazor/Templates" target="_blank">MudBlazor.Templates</a>。</p></blockquote><h3 style="text-align: left;">1. 安裝 MudBlazor 套件</h3><p>指令:<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px; white-space: break-spaces;">dotnet add package MudBlazor</span></p><p><br /></p><p>或者在 Visual Studio 中加入套件:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdxpf4BYemxnX6OL-gXdLdIb3aiKB0vtO2Sdv9g3raPz4uNnqWrdLFcdewgyt9UHF2U60lz7GHL9apGAQjkN_oMff1rkqqTGLp2NZ64W4xwrPj6fE7zDrX9V_lkbqliuK4Vpx5PB9RKsEmoL24k7vQp3X19gllOe5Xs5_z1vYHoROjVSYCYTY7voq0/s764/vs-add-mudblazor.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="408" data-original-width="764" height="342" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdxpf4BYemxnX6OL-gXdLdIb3aiKB0vtO2Sdv9g3raPz4uNnqWrdLFcdewgyt9UHF2U60lz7GHL9apGAQjkN_oMff1rkqqTGLp2NZ64W4xwrPj6fE7zDrX9V_lkbqliuK4Vpx5PB9RKsEmoL24k7vQp3X19gllOe5Xs5_z1vYHoROjVSYCYTY7voq0/w640-h342/vs-add-mudblazor.png" width="640" /></a></div><p><br /></p><h3 style="text-align: left;">2. 加入常用命名空間至 _Imports.razor</h3><p>修改專案根目錄下的 _Imports.razor,加入:</p><p><br /></p><pre class="notranslate" style="background-color: #eff0f1; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">@using MudBlazor</code></pre><p>參考下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilGM90TpAfLtyfq-6__-13rpZglBtP_tYpLPBwLFuyQ-kfjBk9Y2-0G0YXxJZuZN42qXvvQjGCO91Bm8nu1eyABcKbpY0_4_UIe03DVU_-i8OepYPz8m71L6spl_oWzwnJ6cm-TGTEGlMzPptlCnhGkjiyyBMCaJ7MC5w7yDnUtElmGlV75BB8WJ_T/s627/add-using-to-imports.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="215" data-original-width="627" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilGM90TpAfLtyfq-6__-13rpZglBtP_tYpLPBwLFuyQ-kfjBk9Y2-0G0YXxJZuZN42qXvvQjGCO91Bm8nu1eyABcKbpY0_4_UIe03DVU_-i8OepYPz8m71L6spl_oWzwnJ6cm-TGTEGlMzPptlCnhGkjiyyBMCaJ7MC5w7yDnUtElmGlV75BB8WJ_T/s16000/add-using-to-imports.png" /></a></div><p><br /></p><h3 style="text-align: left;">3. 加入字體和樣式連結</h3><p>依你的 Blazor 專案類型而定,修改 index.html 或 _Layout.cshtml 或 _Host.cshtml,在 head 元素中加入 CSS 連結:</p><p><br /></p>
<script src="https://gist.github.com/huanlin/f8b6fdae986a99d2cfcf7237086aae40.js"></script>
<p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPKPUkWv9oLV2lTmT3Asf1wXrTcHGQcyd2vZGP3mZ8-ipNFYoME7HOYRWHukujGlSVCKlRF9PDY74ibnMnx8VEUB4tOgcK4UUqQ6XAmWlGMcJ3ddElSgpoZiTzf-N9M6wbejm3oYGWxsO2bG_Ts0VNRXD1lt7rqpQpfk6b3bXyRm2xTRzc7S37BfH7/s892/add-font-and-css.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="352" data-original-width="892" height="252" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPKPUkWv9oLV2lTmT3Asf1wXrTcHGQcyd2vZGP3mZ8-ipNFYoME7HOYRWHukujGlSVCKlRF9PDY74ibnMnx8VEUB4tOgcK4UUqQ6XAmWlGMcJ3ddElSgpoZiTzf-N9M6wbejm3oYGWxsO2bG_Ts0VNRXD1lt7rqpQpfk6b3bXyRm2xTRzc7S37BfH7/w640-h252/add-font-and-css.png" width="640" /></a></div><p><br /></p><h3 style="text-align: left;">4. 加入 JS</h3><p>修改 wwwroot 資料夾底下的 index.html,於 body 元素結尾之前加入:</p><p><br /></p><pre class="notranslate" style="background-color: #eff0f1; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><script src="_content/MudBlazor/MudBlazor.min.js"></script></code></pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEXb1wEPCyRH0NFllGxkLdhr0IP9fEolxc5sOeBfxjpRBh6CF406Rk5xRClzSXR_RB2PDsEEUpEtKzIiBLnv531dnbvZgIc3VMhbOcv0zdSaWzbPBZxYXegpY96iNn0YJTXooQW3zF5su3Sps465NVNm4g3FL8J_UqPktFGoTGwqvOugxj4cF_N29o/s723/add-js.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="318" data-original-width="723" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEXb1wEPCyRH0NFllGxkLdhr0IP9fEolxc5sOeBfxjpRBh6CF406Rk5xRClzSXR_RB2PDsEEUpEtKzIiBLnv531dnbvZgIc3VMhbOcv0zdSaWzbPBZxYXegpY96iNn0YJTXooQW3zF5su3Sps465NVNm4g3FL8J_UqPktFGoTGwqvOugxj4cF_N29o/w640-h282/add-js.png" width="640" /></a></div><p><br /></p><h3 style="text-align: left;">5. 移除 Bootstrap 與 Open-Iconic(非必要)</h3><p>刪除 wwwroot\css 目錄下的兩個資料夾:</p><p></p><ul style="text-align: left;"><li>bootstrap</li><li>open-iconic</li></ul><p></p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjglRO71sa9rOUzHMOKLBoWc5mzUEAQlYyFykOSNTdLQ-_VXvm8w42c1-x0FcFBh6j75aq9M8fnV_6TLxKLTSDX9-g4vYB4iuRy5dk2NVc6lfOSkKOfddzS39L7z0PcOtYq_qYwO0Xc4k1LkGKJ59vUGg-q4ll9U69Tmk2dblXjPfdUnOn8hCOZ3iUQ/s419/remove-bootstrap-open-iconic.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="419" data-original-width="372" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjglRO71sa9rOUzHMOKLBoWc5mzUEAQlYyFykOSNTdLQ-_VXvm8w42c1-x0FcFBh6j75aq9M8fnV_6TLxKLTSDX9-g4vYB4iuRy5dk2NVc6lfOSkKOfddzS39L7z0PcOtYq_qYwO0Xc4k1LkGKJ59vUGg-q4ll9U69Tmk2dblXjPfdUnOn8hCOZ3iUQ/s16000/remove-bootstrap-open-iconic.png" /></a></div><br /><p><br /></p><p>官方文件說這個步驟不是絕對必要,所以我是暫且保留這兩個資料夾。</p><p><br /></p><h3 style="text-align: left;">6. 註冊基礎服務</h3><p>在 Program.cs 中加入:</p><p><br /></p><pre class="notranslate" style="background-color: #eff0f1; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">using MudBlazor.Services;
builder.Services.AddMudServices();</code></pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhc6mLN1QjHbfM0z_DJuWmXhfKH8w5kb7EjKC1adgIjnT2ovf3FOAWUn43QaSKUItTJozdaBBXkQnd3wqLLQS8rIKg2AMbD7BbmxUW8iu_pfowLiRg5v32uqHk4_OPqcZiy9qYYoSHqx-bsXxehDH-EDHRRcMQUXvWKwPqIL9JPbRV_bhsYlxsLda-z/s780/add-services.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="311" data-original-width="780" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhc6mLN1QjHbfM0z_DJuWmXhfKH8w5kb7EjKC1adgIjnT2ovf3FOAWUn43QaSKUItTJozdaBBXkQnd3wqLLQS8rIKg2AMbD7BbmxUW8iu_pfowLiRg5v32uqHk4_OPqcZiy9qYYoSHqx-bsXxehDH-EDHRRcMQUXvWKwPqIL9JPbRV_bhsYlxsLda-z/w640-h256/add-services.png" width="640" /></a></div><p><br /></p><h3 style="text-align: left;">7. 修改預設的版面配置</h3><p>修改 Shared 資料夾底下的 MainLayout.razor:</p><p><br /></p>
<pre class="notranslate" style="background-color: #eff0f1; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">@inherits LayoutComponentBase
<MudThemeProvider />
<MudDialogProvider />
<MudSnackbarProvider />
<MudLayout>
<MudAppBar>
BlazorApp1
</MudAppBar>
<MudDrawer Open="true">
<NavMenu />
</MudDrawer>
<MudMainContent>
<MudContainer Fxied="true">
@Body
</MudContainer>
</MudMainContent>
</MudLayout></code></pre><p>如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixwNTU-Avwid39P_fjSAi92KWDfFL7GGXiPavoVkXJfTjYjL1Q6otPMD_6FhkDuQ-t0JbxvIusVdI6jZEmIW8iD4r0pV1pN5EbQGWpzuOKwOs2tV6YJO9_FMLVPcY4-DW42oWiMsL74D8xlR4GeeFcD5dWWj8zDGNFdT2RbQhpfmll1-Ilf1CzlEoe/s461/replace-main-layout.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="461" data-original-width="459" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixwNTU-Avwid39P_fjSAi92KWDfFL7GGXiPavoVkXJfTjYjL1Q6otPMD_6FhkDuQ-t0JbxvIusVdI6jZEmIW8iD4r0pV1pN5EbQGWpzuOKwOs2tV6YJO9_FMLVPcY4-DW42oWiMsL74D8xlR4GeeFcD5dWWj8zDGNFdT2RbQhpfmll1-Ilf1CzlEoe/s16000/replace-main-layout.png" /></a></div><br /><p>其中的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px; white-space: break-spaces;">MudContainer</span> 元件,稍後也可以改成這樣看看結果有何不同:</p><p><br /></p><pre class="notranslate" style="background-color: #eff0f1; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><MudContainer MaxWidth=“MaxWidth.Medium">
@Body
</MudContainer> </code></pre><h3 style="text-align: left;">8. 修改左側選單</h3><p>修改 Shared 資料夾底下的 NavMenu.razor:</p><p><br /></p><pre class="notranslate" style="background-color: #eff0f1; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><MudNavMenu>
<MudNavLink Href="/" Match="NavLinkMatch.All">首頁</MudNavLink>
<MudNavGroup Title="子選單" Expanded="true">
<MudNavLink Href="/counter" Match="NavLinkMatch.Prefix">Counter</MudNavLink>
<MudNavLink Href="/fetchdata" Match="NavLinkMatch.Prefix">Fetch Data</MudNavLink>
</MudNavGroup>
<MudNavLink Href="/about" Match="NavLinkMatch.Prefix">關於</MudNavLink>
</MudNavMenu></code></pre><p>完成此步驟之後,執行看看,結果如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIyNNMe6R0_girtZoB1Q-0-XLAsl_0Z-QN5SJOeL0EKHVw2deTJWLATVm-c56Tj5iJ9dhn7rUa1EiiPMP-pKKPQ4Un5ImYOTpgOMl3LCG-7zqj1RD3mynj_Hg7bwND56ng32-EZA59cUuUJ0BwsC2ibvnKEU4DDbAQWZguASL-H7lT22cWCGsy37eV/s1203/app-result-1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="562" data-original-width="1203" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIyNNMe6R0_girtZoB1Q-0-XLAsl_0Z-QN5SJOeL0EKHVw2deTJWLATVm-c56Tj5iJ9dhn7rUa1EiiPMP-pKKPQ4Un5ImYOTpgOMl3LCG-7zqj1RD3mynj_Hg7bwND56ng32-EZA59cUuUJ0BwsC2ibvnKEU4DDbAQWZguASL-H7lT22cWCGsy37eV/w640-h298/app-result-1.png" width="640" /></a></div><br /><h3 style="text-align: left;">9. 改用 MudDataGrid 元件(beta)</h3><p>修改 Pages 資料夾底下的 FetchData.razor:</p><p><br /></p><pre class="notranslate" style="background-color: #eff0f1; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<MudDataGrid Items="@forecasts">
<Columns>
<Column T="WeatherForecast" Field="Date" Title="Date" />
<Column T="WeatherForecast" Field="TemperatureC" Title="Temp. (C)" />
<Column T="WeatherForecast" Field="TemperatureF" Title="Temp. (F)" />
<Column T="WeatherForecast" Field="Summary" Title="Summary" />
</Columns>
</MudDataGrid>
}</code></pre><p>再執行看看:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgb_6N6E6H1-9ZXNVCqgrqg5qGnYLp7ltzaWA9yUm4npe01nZF5EcwiJv4lRi9im1qEo2TCqLCDmfSdHQAnVYOkH78sbhirCXw6XZFrzINDPd9qkgqNWA7ZWR7XfNb72gkeid1XY6LgVHhBexnbUh8rrxCBrxjH2fCi-iO2Xkh8YK1ys4XEyBJ9vWRx/s1205/after-mudblazor.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="622" data-original-width="1205" height="330" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgb_6N6E6H1-9ZXNVCqgrqg5qGnYLp7ltzaWA9yUm4npe01nZF5EcwiJv4lRi9im1qEo2TCqLCDmfSdHQAnVYOkH78sbhirCXw6XZFrzINDPd9qkgqNWA7ZWR7XfNb72gkeid1XY6LgVHhBexnbUh8rrxCBrxjH2fCi-iO2Xkh8YK1ys4XEyBJ9vWRx/w640-h330/after-mudblazor.png" width="640" /></a></div><br /><p><br /></p><p>寫作本文時,<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px; white-space: break-spaces;">MudDataGrid</span> 元件目前仍在 beta 階段,意味著將來可能會有較大幅度的改動(可能有 breaking changes)。它有許多進階功能,包括:搜尋文字、排序、篩選、進階篩選、分頁、資料群組(grouping)、聚合(aggregation)、奇偶列顏色替換、凍結欄、<a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/components/virtualization?view=aspnetcore-7.0" target="_blank">虛擬化</a>(提升效能)等等,詳見<a href="https://mudblazor.com/components/datagrid" target="_blank">官方文件:Data Grid</a>。</p><p><br /></p><p>順便提及,Blazor 小組目前也正在開發一個 data grid 元件,叫做 <a href="https://aspnet.github.io/quickgridsamples/" target="_blank">QuickGrid</a>。此元件目前仍在 beta 階段,可預期將來會正式納入 Blazor 套件。</p><h2 style="text-align: left;">下一步</h2><div>就如前面提過的,MudBlazor 是基於 Material Design,所以還是需要了解相關的基本知識,比如說「調色盤」是按照功能來劃分顏色群組,而各群組的顏色名稱有 Primary(產品主色調)、Secondary(副色調)、Tertiary(讀音類似「忒許A-ry」,第三色調)、Background(背景色)、Surface(表層色)、Error(用於顯示錯誤訊息的顏色)……等等。了解這些排版與樣式的基本觀念,將有助於<a href="https://mudblazor.com/customization/default-theme" target="_blank">使用 MudBlazor 來設計不同風格的網頁</a>。</div><div><br /></div><div>像我這樣不懂配色的人,如果有現成工具可以幫我產生一組看起來舒服協調的顏色,那就太棒了。例如 <a href="https://www.materialpalette.com/" target="_blank">materialpalette.com</a>,這個網站可以讓我任意挑選兩個顏色,然後它會自動建立一組配色,如下圖:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbG8mj7xxqQM26XHM6RllA0bCXt6DV3xbq8Mrcl6KkghZ0jVx7WSQ3pchqQ1g0deUCUfXwmq9UjoDHutYcHfrHAAGmwfwa-3aw8ziXpSai9loqJJfyq1Enq5luBz8lHivGxogomy8jdUfjLaBhvxRtWmq0ajMRtMK2nZgv9FFCcxCEp8V10olitu9o/s1920/palette.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="957" data-original-width="1920" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbG8mj7xxqQM26XHM6RllA0bCXt6DV3xbq8Mrcl6KkghZ0jVx7WSQ3pchqQ1g0deUCUfXwmq9UjoDHutYcHfrHAAGmwfwa-3aw8ziXpSai9loqJJfyq1Enq5luBz8lHivGxogomy8jdUfjLaBhvxRtWmq0ajMRtMK2nZgv9FFCcxCEp8V10olitu9o/w640-h320/palette.png" width="640" /></a></div><br /><div><br /></div><div>頁面右下方會顯示當前配色的各個顏色名稱與色碼。雖然沒有完全涵蓋 MudBlazor 的全部顏色名稱,但作為一個調色基礎,還是挺方便的。</div><div><br /></div><div>又如 Material Design 網站所提供的 <a href="https://m2.material.io/resources/color/" target="_blank">Color Tool</a>,也有類似功能,如下圖:</div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioJ8_8aqIm9F1sn-kOrM75iRF94uBWaubAEr2plIp6a1bXT8ynpG-1hUJsARGjCUhiI6f5yf2vC9CF1H0-HVagC4oFfrdhRkHYz49b0knU9wx9_qN6YK8QmqU7jeUjkUDdIzWXnKB3xq0t2q98XCVEDd5nGaY_TbFGC6u-yuff5yF0i5i0XKBQhMnn/s1212/color-tool.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="784" data-original-width="1212" height="414" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioJ8_8aqIm9F1sn-kOrM75iRF94uBWaubAEr2plIp6a1bXT8ynpG-1hUJsARGjCUhiI6f5yf2vC9CF1H0-HVagC4oFfrdhRkHYz49b0knU9wx9_qN6YK8QmqU7jeUjkUDdIzWXnKB3xq0t2q98XCVEDd5nGaY_TbFGC6u-yuff5yF0i5i0XKBQhMnn/w640-h414/color-tool.png" width="640" /></a></div><br /><div><br /></div><div>還有 <a href="https://mudblazor.com/features/colors#material-colors-list-of-material-colors" target="_blank">MudBlazor 提供的色碼速查表</a>,先記下來,將來應該也能派上用場。</div><h2 style="text-align: left;">結語</h2><p>MudBlazor 本身的官方文件寫得清楚易懂,還有提供<a href="https://try.mudblazor.com/" target="_blank">線上遊樂場</a>來做一些簡單快速的小實驗,相當貼心。說明文件的品質是我挑選第三方元件庫的主要考量之一。</p><p><br /></p><p>我想,完成本文的入門練習後,往後寫程式只要參考 <a href="https://mudblazor.com/docs/overview" target="_blank">MudBlazor 官方文件</a> 就行了。故有關 MudBlazor 的筆記,也許就僅此一篇吧。</p><p><br /></p><p>Happy coding with MudBlazor! (<strike><span style="color: #666666;">媽的不累惹</span></strike>)</p>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-77149912701666018312022-12-19T15:39:00.018+08:002022-12-22T13:15:38.650+08:00部署 Blazor WebAssembly app 至 GitHub Pages<p>我的 .NET Blazor 筆記,第 3 篇。</p>
<span><a name='more'></a></span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitC7SCtb4Oa91h7tjRi9BkkgjVmjwU65oaLOtHzdD5vZBY2dsRlNuVbF9GYnO32ikoc7plJjFeyXiVMpPKObXngo7bjZhkQekVSyZ1kYnu9vQIkZFHJQXjSoqTCPn5ASj3g06b-cM8YBfMmSTcBmwABCYPpXRFFys6I6epRfUt9Jo8N_P11zbYJQbd/s617/article-banner-3.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="452" data-original-width="617" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitC7SCtb4Oa91h7tjRi9BkkgjVmjwU65oaLOtHzdD5vZBY2dsRlNuVbF9GYnO32ikoc7plJjFeyXiVMpPKObXngo7bjZhkQekVSyZ1kYnu9vQIkZFHJQXjSoqTCPn5ASj3g06b-cM8YBfMmSTcBmwABCYPpXRFFys6I6epRfUt9Jo8N_P11zbYJQbd/s16000/article-banner-3.png" /></a></div><h2>如何使用這個筆記</h2><div>按目前的想法,我的 Blazor 筆記頂多只會觸及一些基本功。如果你正打算開始學習 Blazor,這些筆記或許有點幫助。我建議碰到需要動手寫程式的時候就照著範例寫一遍;如果發現我沒寫清楚的地方,就查 google 或閱讀 learn.microsoft.com 的<a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/" target="_blank">技術文件</a>與<a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/tutorials/" target="_blank">教學課程</a>。</div><div><br /></div><div><div><div>底下是本系列各篇筆記的連結:</div><div><ol><li><a href="https://www.huanlintalk.com/2022/12/net-blazor-introduction.html" target="_blank">簡介</a></li><li><a href="https://www.huanlintalk.com/2022/12/net-blazor-your-first-app-with-custom.html" target="_blank">拆解專案結構、自訂版面配置</a> </li><li>部署 Blazor WASM app 至 GitHub Pages(你目前所在位置)</li><li><a href="https://www.huanlintalk.com/2022/12/getting-started-with-mudblazor.html" target="_blank">MudBlazor 快速上手</a></li></ol></div></div><div><br /></div><div>相關參考資料已經寫在第一集〈<a href="https://www.huanlintalk.com/2022/12/net-blazor-introduction.html" target="_blank">.NET Blazor 筆記:簡介</a>〉,這裡就不再重複。</div><div><br /></div><div><b>開發工具與版本:</b>.NET 7 SDK,Visual Studio 2022 17.5.0 (OS:Windows 11)</div></div><div><br /></div>
<hr />
<p><b>內容大綱:</b></p><p></p><ul style="text-align: left;"><li>前言</li><li>GitHub Pages 簡介</li><li>建立部署專用的分支:gh-pages</li><li>部署你的 Blazor WASM App</li><li>修正之一:base 標籤</li><li>修正之二:關閉 Jekyll</li><li>修正之三:用 index.html 取代 404 頁面</li><li>補充:部署至 Azure Static Web Apps</li><li>下一步</li></ul><div>(本文包含一個 Youtube 示範影片)</div><p></p>
<hr />
<h2 style="text-align: left;">前言</h2><p>「才剛寫完一個超級陽春的 Blazor app,這麼快就要把它部署到網站主機?」</p><p><br /></p><p>Blazor 應用程式的兩種裝載模型(hosting model)決定了將來的部署方式,故我認為越早部署越好。盡早練習部署可能對個人帶來以下好處:</p><p></p><ul style="text-align: left;"><li>更深刻理解 Blazor 裝載模型。</li><li>有助於概念驗證(proof of concept)。</li><li>盡早完成最簡可行產品(minimum viable product)。</li><li>提早發現架構層面的需求與限制。</li></ul><p><br /></p><p>儘管如此,以目前這個的筆記的撰寫進度來看,我覺得並不適合涵蓋所有部署議題,特別是 Blazor Server app 的部分,所以本文介紹的部署操作都是針對 standalone Blazor WebAssembly app。等到介紹 Blazor Server app 的時候,會再一併提及有關部署的相關事項。</p><p><br /></p><p>讀完本文並完成相關的練習之後,您應該能把自己的 Blazor WebAssembly app 部署至 GitHub Pages。</p><p><br /></p><h2 style="text-align: left;">GitHub Pages 簡介</h2><p>GitHub 是一個多人協作與 git 原始碼版本控制平台,有免費與付費方案。對於個人或小型開發團隊而言,它提供的免費服務算是相當夠用,包括自動建置、靜態網站等等,都是免費的。其中的靜態網頁服務叫做 GitHub Pages。</p><p><br /></p><p>欲將 standalone Blazor WebAssembly app 部署至 GitHub Pages,首先必須在 GitHub 平台上建立一個儲存庫(repository),或簡稱 <b>repo</b>。儲存庫可以直接建立在你的 GitHub 帳戶之下,或者你也可以先<a href="https://github.com/account/organizations/new" target="_blank">建立一個組織</a>,然後在組織底下建立你的專案原始碼的 repo。</p><blockquote><p>當然你必須先<a href="https://github.com/signup" target="_blank">註冊一個 GitHub 帳號</a>,才能進行本文介紹的相關操作。此外,這裡會假設您已具備 git 版本控制的基礎觀念與一些常用命令,故不會詳細解釋每一個 git 命令或操作。</p></blockquote><p><br /></p><p>當你在 GitHub 平台上建立一個 repo 時,除了指定儲存庫的名稱,它還會讓你選擇誰可以看見你的儲存庫。如果選擇「public」(公開),就表示任何人都能看見該儲存庫的所有檔案;若選擇「private」(私人),則表示該儲存庫只有你能看見。</p><p><br /></p><p>那麼,我們要在 GitHub Pages 上面部署靜態網頁,自然是要給所有人都能看見,所以 repo 也就必須設定為公開存取,對嗎?那樣一來,我的程式碼不就給別人看光光了嗎?</p><p><br /></p><p>的確,如果你的 GitHub 帳號是免費的,那麼你的 repo 必須是公開的,才能夠使用 GitHub Pages 功能。如果是付費的 GitHub 會員,則無論 repo 是 public 還是 private,便都能夠使用 GitHub Pages。</p><p><span style="color: #cc0000;"><br /></span></p><p><span style="color: #cc0000;">每當需要發布網頁時,只要將靜態網站所需之檔案複製到 GitHub Pages 專屬的分支(branch)就行了。這個專門用來架站用的分支,其預設名稱是「<b>gh-pages</b>」。</span></p><p><br /></p><p>也就是說,只要在你的 repo 中建立一個名為「gh-pages」的分支,GitHub 平台就會知道你要替這個 repo 架設一個公開網站,而且每當那個分支有檔案更新時,平台會自動進行相關的部署操作。如果你想要用其他名稱的分支來部署網站也沒問題,只要進入 repo 的設定頁面就能找到 Pages 的相關設定,如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiL_pqgZA8jQAgRuzaSc36pDysYDDzAOhFDpw-GOl4JMhGW5wBVlG45yQFa89M2q8f9PCwJr45Zejn-bfZ4rOU2KSF6rim82Z7rVLuCrEATJa_lS_SguYAuW3O807aMrF8u2dSMqZnubEPinhzcsBFOZaAzKEMCZxvdanGAhfSz2T5o0tpxb8fdmN_Z/s1460/github-settings-pages-branch.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="798" data-original-width="1460" height="350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiL_pqgZA8jQAgRuzaSc36pDysYDDzAOhFDpw-GOl4JMhGW5wBVlG45yQFa89M2q8f9PCwJr45Zejn-bfZ4rOU2KSF6rim82Z7rVLuCrEATJa_lS_SguYAuW3O807aMrF8u2dSMqZnubEPinhzcsBFOZaAzKEMCZxvdanGAhfSz2T5o0tpxb8fdmN_Z/w640-h350/github-settings-pages-branch.png" width="640" /></a></div><br /><p>由上圖可知我有一個名為 blazor-notes 的私人儲存庫(我的會員帳號是 GitHub Pro 方案),而且建立了一個 gh-pages 分支用來部署靜態網站。你現在就可以開啟那個網站來查看稍後的實戰操作成果,網址是 <a href="https://huanlin.github.io/blazor-notes/">https://huanlin.github.io/blazor-notes/</a>。</p><p><br /></p><h2 style="text-align: left;">建立部署專用的分支:gh-pages</h2><p>在進行後續操作之前,請先確定你已經在自己的 GitHub repo 中建立好一個用來部署用的分支。簡單起見,建議您採用預設的分支名稱,也就是「gh-pages」。你可以用任何自己覺得順手的 Git 圖形化介面工具(例如 TortoiseGit)來完成相關操作,或者參考以下指令:</p><p><br /></p><pre aria-describedby="popover746589" class="part in-view" data-endline="45" data-original-title="" data-position="1884" data-startline="41" style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;" title=""><code style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">git branch gh-pages
git checkout gh-pages
git push --set-upstream origin gh-pages</code></pre><p><br /></p><p>執行最後一道命令之後,就可以在 GitHub 網站上面看到這個新建立的分支。</p><p><br /></p><p>在建立分支以前,如果你的 repo 已經有程式檔案,那麼這些程式檔案也會自動納入這個新建立的 gh-pages 分支,因此,請先把這些你不想要公開的程式檔案與目錄從 gh-page 分支裡面全部刪除(不要刪除隱藏目錄 ".git"),並完成 commit 與 push 至遠端主機的操作。</p><blockquote><p>也許有點多餘,但還是提醒一下:在 gh-pages 分支裡面刪除程式檔案之後,不要把相關變動合併至你的主分支(名稱通常是 main 或者 master)或其他作為程式開發用途的分支,因為那等於把檔案刪除的動作也套用至那些分支。</p></blockquote><p><br /></p><p>以文字和截圖來描述操作步驟,往往沒有影片來得生動、直觀。因此,我另外錄製了一個 4 分鐘的短片,用來展示整個部署的操作過程。若趕時間,不妨先看一下影片,以快速了解部署過程會碰到哪些狀況,以及如何解決。底下是影片(建議<a href="https://www.youtube.com/watch?v=fhWR8MV_f_4" target="_blank">至 Youtube 網站觀看</a>):</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="404" src="https://www.youtube.com/embed/fhWR8MV_f_4" width="485" youtube-src-id="fhWR8MV_f_4"></iframe></div><p><br /></p><h2 style="text-align: left;">部署你的 Blazor WASM App</h2><p>此時,你應該已經在自己的 GitHub repo 中建立了一個名為 gh-pages 的分支,而且該分支裡面除了 git 版控所需的組態檔之外,應該沒有其他檔案。</p><p><br /></p><p>接下來,我們只要把網站所需之檔案複製到該分支的根目錄就行了,對嗎?大致沒錯,但還有三個地方需要手動修正,才能讓我們的 Blazor app 順利運行於瀏覽器。</p><p><br /></p><p>這裡用<a href="https://www.huanlintalk.com/2022/12/net-blazor-your-first-app-with-custom.html" target="_blank">上一篇筆記</a>所建立的第一個 Blazor WebAssembly 專案來展示整個部署過程,當時建立的專案名稱是 BlazorApp1.Client。請參考以下指令來建立部署所需的檔案:</p><p><br /></p><pre aria-describedby="popover873733" class="part in-view" data-endline="64" data-original-title="" data-position="2583" data-startline="61" style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;" title=""><code style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">cd c:\work\blazor-notes\demo\BlazorApp1.Client
dotnet publish -c Release -o c:\temp\BlazorApp1.Client</code></pre><p><br /></p><p>第 1 行命令是將目前工作路徑切換至 BlazorApp1.Client 專案所在的目錄(其中的 blazor-notes 即是我的 repo 根目錄)。第 2 行是以 <span style="background-color: rgba(0, 0, 0, 0.04); color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px;">dotnet publish</span> 來發布專案的建置結果,其中使用了兩個選項:</p><p><br /></p><p></p><ul style="text-align: left;"><li><span style="background-color: rgba(0, 0, 0, 0.04); color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px;">-c</span> 選項:指定使用 Release 模式來建置專案。</li><li><span style="background-color: rgba(0, 0, 0, 0.04); color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px;">-o</span> 選項:指定建置完成的檔案輸出路徑。</li></ul><p></p><p><br /></p><p>如果你偏好在 Visual Studio 中進行上述操作,可以先在 Solution Explorer 中對專案名稱點一下滑鼠右鍵,然後從右鍵選單中點「Publish」。接著會出現一個對話窗,讓你選擇部署方式:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGlfbepizCw-eTnupPuwU6kU6StaacgybzhzbkizPk1UwkM9SbCh0DrcG3rEJLJbrkmtYmfVw15ctItYjGoKuxTbxGmjX5LX1pdeSUZDzrP1aJkWLp628QfOOTuRIGjOkzpOSteQWU8ETJ0O-FWLVQdjlbp3xazOjELgKSA5r9nuvXv_XAv01c8lTW/s1004/vs-publish-to-folder-step1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="734" data-original-width="1004" height="468" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGlfbepizCw-eTnupPuwU6kU6StaacgybzhzbkizPk1UwkM9SbCh0DrcG3rEJLJbrkmtYmfVw15ctItYjGoKuxTbxGmjX5LX1pdeSUZDzrP1aJkWLp628QfOOTuRIGjOkzpOSteQWU8ETJ0O-FWLVQdjlbp3xazOjELgKSA5r9nuvXv_XAv01c8lTW/w640-h468/vs-publish-to-folder-step1.png" width="640" /></a></div><br /><p>在此對話窗選擇「Folder」,表示我們要把專案的建置結果輸出至某個資料夾。接著按 Next (下一步),便可指定輸出路徑,如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIBNV9n1ecimlqKX142zRkv42hGKY66J45zZSnXVRrcvvuPb0Tsh7HiwZWPOT4VWaYNdRtY1fndolWoQfkHPqBhwWRx6DeIypdOnm8SRkdGduVlKIOJzXCFtYt3aNPhVsQ81OJ1HVJ1BFZMbYgnPQSIb96kDimF48UrzLDcBhFjrVeaghFdurns_Cs/s1004/vs-publish-to-folder-step2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="516" data-original-width="1004" height="328" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIBNV9n1ecimlqKX142zRkv42hGKY66J45zZSnXVRrcvvuPb0Tsh7HiwZWPOT4VWaYNdRtY1fndolWoQfkHPqBhwWRx6DeIypdOnm8SRkdGduVlKIOJzXCFtYt3aNPhVsQ81OJ1HVJ1BFZMbYgnPQSIb96kDimF48UrzLDcBhFjrVeaghFdurns_Cs/w640-h328/vs-publish-to-folder-step2.png" width="640" /></a></div><br /><p>點一下 Finish(完成)按鈕,便會開始建置並發佈專案。</p><p><br /></p><p>完成上述操作之後,你應該會在 c:\temp\BlazorApp1.Client\ 資料夾底下看到一個 web.config 檔案,以及 wwwroot 資料夾。部署至 GitHub Pages 時並不需要 www.config(那是給 IIS 用的),我們只需要 wwwroot 資料夾底下的全部檔案。</p><p><br /></p><p>接著請將此專案所在的分支切換至「gh-pages」分支,並將剛才建置專案所產生的檔案複製到 repo 的根目錄。命令如下:</p><p><br /></p><pre aria-describedby="popover570079" class="part in-view" data-endline="90" data-original-title="" data-position="3404" data-startline="86" style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;" title=""><code style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">cd c:\work\blazor-notes
git checkout gh-pages
xcopy /S c:\temp\BlazorApp1.Client\wwwroot\*.*</code></pre><p><br /></p><p>完成上述命令之後,到你的 repo 根目錄(也就是我的 c:\work\blazor-notes)確認一下相關的檔案目錄是否都已經複製過來。</p><p><br /></p><p>接著,同樣在 repo 的根目錄執行以下命令:</p><p><br /></p><pre aria-describedby="popover912204" class="part in-view" data-endline="101" data-original-title="" data-position="3613" data-startline="96" style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;" title=""><code style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">"* binary" >> .gitattributes
git add .
git commit -m "deploy step 1"
git push</code></pre><p><br /></p><p>上述命令的用意是在 repo 根目錄下產生一個名為 ".gitattributes" 的檔案,裡面的內容只有一行,也就是 "<span style="background-color: rgba(0, 0, 0, 0.04); color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px;">* binary</span>"。這會告訴 git:「請把這個分支裡面的所有檔案都當成二進位檔案來處理。」如此一來,無論是 Windows 還是 Linux 作業系統,git 在處理檔案時便不會因為作業系統的差異而去自動轉換程式檔案(純文字檔案)的換行符號(Windows 環境慣用的換行符號是 CRLF,而 Linux 是 LF)。 </p><blockquote><p>如果讓 git 在背後自動修改程式檔案的換行符號,那些被修改過的檔案(例如 blazor.webassembly.js)就會有不同的雜湊值,而 Blazor 一旦發現雜湊值不對,就會拒絕載入檔案。如果有興趣深入考究,可參考:<a href="https://github.com/dotnet/aspnetcore/issues/19796" target="_blank">Git's CRLF transformations interfere with content hashes</a>。</p></blockquote><p><br /></p><p>接著用瀏覽器登入你的 GitHub 帳號,進入你的專案的 repo,並切換至 gh-pages 分支,你看到的頁面內容會像這樣:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1XkYa_ApF3cs5_A1nYc7M2kIAcf7dTMuRq7K77PqxLGQpNQIN5S4wJgHSf6rF7Quv46GjLlOklNiXdra673L7MWXdg7rackmkfqrabeeaGy-QlCWNc_UzXLfFOjatIXgGyuofbyEc7vbdLMEDOzzKqUR4t9TqHPUzFLDuY0KjgCn-dt_ikTK7kH3f/s836/deploy-to-gh-pages-step1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="584" data-original-width="836" height="448" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1XkYa_ApF3cs5_A1nYc7M2kIAcf7dTMuRq7K77PqxLGQpNQIN5S4wJgHSf6rF7Quv46GjLlOklNiXdra673L7MWXdg7rackmkfqrabeeaGy-QlCWNc_UzXLfFOjatIXgGyuofbyEc7vbdLMEDOzzKqUR4t9TqHPUzFLDuY0KjgCn-dt_ikTK7kH3f/w640-h448/deploy-to-gh-pages-step1.png" width="640" /></a></div><br /><p>上方橫排有一個 Actions 連結,由此連結點進去,便可查看 GitHub 自動執行的部署工作與狀態。一旦確認 GitHub 的自動部署工作完成,便可用瀏覽器開啟你的 repo 的 GitHub Pages 網頁(前面提過可以在 repo 的 Settings > Pages 頁面找到這個網址)。</p><p><br /></p><p>然而,開啟網頁時卻出現錯誤:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2AyMBFcjd8bO7uLGD-bdiEFZMuIHWZXQI3KmylkEX3WADzu6IShuCRlIMzlP_7THdvpeYxGsyDZctrnulCzBCsb3Qwl8PVScJeIhXPSgRjjrEByChDS8eB8bkaMKQzTefNjO2rXhGfZaiF0i4xOaXroAWm0bv-EKOJbRV31GDj_K2C_RBiR8QLjZV/s741/deploy-to-gh-pages-error1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="391" data-original-width="741" height="338" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2AyMBFcjd8bO7uLGD-bdiEFZMuIHWZXQI3KmylkEX3WADzu6IShuCRlIMzlP_7THdvpeYxGsyDZctrnulCzBCsb3Qwl8PVScJeIhXPSgRjjrEByChDS8eB8bkaMKQzTefNjO2rXhGfZaiF0i4xOaXroAWm0bv-EKOJbRV31GDj_K2C_RBiR8QLjZV/w640-h338/deploy-to-gh-pages-error1.png" width="640" /></a></div><br /><p>怎麼回事?</p><p><br /></p><h2 style="text-align: left;">修正之一:base 標籤</h2><p>打開瀏覽器的除錯視窗,可以看到裡面有四個 404 錯誤,如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT1cskgjBphvGRCzMkeZtHWZX1DVY26__DdbShL572nQ7Osa9BuDUUmS4_tFUPXgt9sqhgknAuMuq_Xr81V5AC0ug6WkCGd-EW_eo40FMOY7J02DnEkPA6TNn9WDhwvWRtfpi28LvuyjodUBDZlXrQ_Zd8Masvzgs6b8qoQjBUokmO-cjumqBFIaQ4/s825/deploy-to-gh-pages-browser-debug.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="433" data-original-width="825" height="336" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT1cskgjBphvGRCzMkeZtHWZX1DVY26__DdbShL572nQ7Osa9BuDUUmS4_tFUPXgt9sqhgknAuMuq_Xr81V5AC0ug6WkCGd-EW_eo40FMOY7J02DnEkPA6TNn9WDhwvWRtfpi28LvuyjodUBDZlXrQ_Zd8Masvzgs6b8qoQjBUokmO-cjumqBFIaQ4/w640-h336/deploy-to-gh-pages-browser-debug.png" width="640" /></a></div><br /><p>問題出在我們的 index.html 裡面的 <span style="background-color: rgba(0, 0, 0, 0.04); color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px;">base</span> 標籤所設定的根網址是 "/",故瀏覽器會嘗試從 https://huanlin.github.io 這個根網址來載入相關檔案。然而,我們部署的檔案實際上並非存在 *.github.io/ 底下,而是在 *.github.io/<span style="color: #cc0000;"><b>[repo]</b></span>/ 底下。</p><p><br /></p><p>解決方法是修改 gh-pages 分支的根目錄底下的 index.html,把 <span style="background-color: rgba(0, 0, 0, 0.04); color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px;">base</span> 標籤的 href 屬性值從原本的 "/" 改為你的 repo 名稱,而且前後都必須加上 "/"。如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfDWmSvPAvsUcb42buVJ7xc_sgNEIxyQuvKc0opXsLvPoc-R5op5Th7fXk_doO5nzbl7V7dE0swV9w_m_45zPG_xTLoAnmvQb3zYIyd7IjOtmpys3ZvKKGqmULGshKYWKwo1BfLSqFgAcmEYTXrj-gMzDAtlSq6K9VWkOiEMQV0Wg_fxDm7UTTL5fZ/s949/deploy-to-gh-pages-fix-base-tag.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="355" data-original-width="949" height="239" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfDWmSvPAvsUcb42buVJ7xc_sgNEIxyQuvKc0opXsLvPoc-R5op5Th7fXk_doO5nzbl7V7dE0swV9w_m_45zPG_xTLoAnmvQb3zYIyd7IjOtmpys3ZvKKGqmULGshKYWKwo1BfLSqFgAcmEYTXrj-gMzDAtlSq6K9VWkOiEMQV0Wg_fxDm7UTTL5fZ/w640-h239/deploy-to-gh-pages-fix-base-tag.png" width="640" /></a></div><br /><p><br /></p><p>修改完成並存檔,然後推送至遠端儲存庫:</p><p><br /></p><pre aria-describedby="popover729459" class="part in-view" data-endline="137" data-original-title="" data-position="4770" data-startline="133" style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;" title=""><code style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">git add .
git commit -m "Fix base tag"
git push</code></pre><p><br /></p><p>等待 GitHub 的自動部署工作完成,然後再次開啟 GitHub Pages 首頁。這次有了嗎?</p><p><br /></p><p>結果還是不行!這次看到網頁上出現「Loading」圖案之後就沒動靜了。</p><p><br /></p><h2 style="text-align: left;">修正之二:關閉 Jekyll</h2><p>從瀏覽器的除錯視窗可以看到這次仍有一個檔案找不到:blazor.webasembly.js。如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTRH6iAtyqQGUtaOmYLpNY1_BURPRH8MccHTjZvziStsp8m0K2ZABq5QkkK5pFmQXzRQeECoaFJGfVw5lIt5wgDQ-pIzu31Q4fuFJxPEoRAMskS3hW3XxVSPCxNuITkOjvwdsxrqGIqsvulLQ5h0YZD7djQl8a8JCQckW6EmggZtZ-Oxe0qd-AcJ8G/s1053/deploy-to-gh-pages-error2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="585" data-original-width="1053" height="356" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTRH6iAtyqQGUtaOmYLpNY1_BURPRH8MccHTjZvziStsp8m0K2ZABq5QkkK5pFmQXzRQeECoaFJGfVw5lIt5wgDQ-pIzu31Q4fuFJxPEoRAMskS3hW3XxVSPCxNuITkOjvwdsxrqGIqsvulLQ5h0YZD7djQl8a8JCQckW6EmggZtZ-Oxe0qd-AcJ8G/w640-h356/deploy-to-gh-pages-error2.png" width="640" /></a></div><br /><p><br /></p><p>這個 blazor.webasembly.js 是放在一個名為「_framework」的目錄底下,而問題就出在 GitHub Pages 預設的靜態網頁產生器「[Jekyll](https://github.com/jekyll)」會自動忽略所有以底線開頭的子目錄。「_framework」資料夾的第一個字元就是底線。</p><p><br /></p><p>解決方法是在 gh-pages 分支的根目錄下建立一個名為「.nojekyll」的檔案。檔案內容不重要,空白即可。參考以下命令:</p><p><br /></p><pre aria-describedby="popover461056" class="part in-view" data-endline="156" data-original-title="" data-position="5239" data-startline="151" style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;" title=""><code style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">"" >> .nojekyll
git add .
git commit -m "Disable Jekyll"
git push</code></pre><p><br /></p><p>等待 GitHub 的自動部署工作完成,然後再次開啟 GitHub Pages 首頁,這次應該就能順利顯示首頁了。</p><blockquote><p>你也可以到我的 GitHub Pages 查看執行結果,網址是 <a href="https://huanlin.github.io/blazor-notes/">https://huanlin.github.io/blazor-notes/</a>。</p></blockquote><p><br /></p><p>等等!先別急著收工,還有一個地方要修正。</p><p><br /></p><h2 style="text-align: left;">修正之三:用 index.html 取代 404 頁面</h2><p>目前為止,雖然可以瀏覽器中順利呈現部署後的網頁,但其實還有一個問題需要解決。按照下圖的提示來操作,便可令 404 錯誤再度出現:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigctEnLrb6nmwYV7OgmpE8GIBiK6gGWSszVd-CgcSq5-FVzvgyt1t7w3XdrWQA3Oy6gQ7xT_LcTqeYcUhL-3glfJZ-rIUq6stCRntKsxKBgnjWBKHh0T6dMNZ6x8vgoSPNvDn42QgDEB0dXm88O9SlnRR0N83vP4ahwER1uKokgUoyGWPcC5RbASoF/s718/deploy-to-gh-pages-error3.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="502" data-original-width="718" height="448" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigctEnLrb6nmwYV7OgmpE8GIBiK6gGWSszVd-CgcSq5-FVzvgyt1t7w3XdrWQA3Oy6gQ7xT_LcTqeYcUhL-3glfJZ-rIUq6stCRntKsxKBgnjWBKHh0T6dMNZ6x8vgoSPNvDn42QgDEB0dXm88O9SlnRR0N83vP4ahwER1uKokgUoyGWPcC5RbASoF/w640-h448/deploy-to-gh-pages-error3.png" width="640" /></a></div><p><br /></p><p>這是因為在 Counter 頁面中按 F5 來重新載入頁面時,GitHub 會嘗試去找 "Counter" 檔案的緣故。</p><p><br /></p><p>針對此問題,有一個比較簡便的解決方法,就是在根目錄底下放一個 404.html 檔案,而檔案內容是直接從我們的 index.html 複製而來。命令如下:</p><p><br /></p><pre aria-describedby="popover342786" class="part in-view" data-endline="179" data-original-title="" data-position="5739" data-startline="174" style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;" title=""><code style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">copy index.html 404.html
git add .
git commit -m "Fix 404 page"
git push</code></pre><p><br /></p><p>大功告成!</p><blockquote><p>以上描述的操作步驟若有不清楚的地方,可參考<a href="https://youtu.be/fhWR8MV_f_4" target="_blank">我錄製的 Youtube 短片</a>。</p></blockquote><h2 style="text-align: left;">補充:部署至 Azure Static Web Apps</h2><p>相較於 GitHub Pages,把 Blazor apps 部署到 Azure Static Web Apps 可以說是小菜一碟。微軟網站上面已經有教學文章:<a href="https://learn.microsoft.com/zh-tw/azure/static-web-apps/deploy-blazor" target="_blank">使用 Blazor 和無伺服器 API 建置 Azure Static Web Apps 網站</a>。依照文那篇文章來設定,順利的話,通常半個小時以內便可完成初次的部署設定。</p><p><br /></p><p>下圖是我在 Azure Static Web Apps 服務中建立的一個名為 "blazorapp1" 的應用程式的「概觀」頁面:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuBoZQtQO9JWUC9yOpYhJj7pfOyLyUP7NmvYyygok3TMkJfv7Dk4pmbLOX_RmMGdWC6UH790L4l08Hzs-8KZu-CXy01jdpsawhUZYL4dovK4M-fav3htqnHfpuxs8QeXGvcWPSl3tXuWUv2hcTXtGJzMByqNpRXPuvEqxUwWBSAd6KTUuSupkNUgtk/s1251/my-azure-static-web-overview.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="636" data-original-width="1251" height="326" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuBoZQtQO9JWUC9yOpYhJj7pfOyLyUP7NmvYyygok3TMkJfv7Dk4pmbLOX_RmMGdWC6UH790L4l08Hzs-8KZu-CXy01jdpsawhUZYL4dovK4M-fav3htqnHfpuxs8QeXGvcWPSl3tXuWUv2hcTXtGJzMByqNpRXPuvEqxUwWBSAd6KTUuSupkNUgtk/w640-h326/my-azure-static-web-overview.png" width="640" /></a></div><br /><p><br /></p><p>雖然相關設定並不複雜,但仍有需要留意、和手動調整的地方。</p><p><br /></p><p>需要留意的是:在設定建置腳本時,Azure 不需要像 gh-pages 那樣的分支,而是可以直接指定要用哪個原始碼分支來部署。Azure 平台所產生的建置腳本會自動從我們指定的分支取出原始碼來建置專案,而其中的一個關鍵設定是 <span style="background-color: rgba(0, 0, 0, 0.04); color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px;">app_location</span>。</p><p><br /></p><p>在建置腳本中, <span style="background-color: rgba(0, 0, 0, 0.04); color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px;">app_location</span> 所指向的路徑必須有 .csproj 檔案。如果該路徑底下只有 .sln 檔案,建置會失敗(可至 GitHub 儲存庫的 Actions 頁面查看建置狀態)。下圖是我的 BlazorApp1.Client 專案的部署設定檔的部分內容:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgk17hRFd_wa8bd8HwP6ZXQjCeh_QFbYyts_kLAHKHMs9yyV5bJsyv_FO-xpCojJf9My5yO05EUB6MBr_ATdcn4zRUZ2gXRNX2tOwKTJW__n5X3-4LpY40CahIXrqtbHJrnAm3v94apzaRj1Uz_X3jLc6B2rdbyHVfqoiwX6U7nEzwCzy96KZFfY-Ey/s1178/deploy-to-azure-workflow-yml.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="770" data-original-width="1178" height="418" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgk17hRFd_wa8bd8HwP6ZXQjCeh_QFbYyts_kLAHKHMs9yyV5bJsyv_FO-xpCojJf9My5yO05EUB6MBr_ATdcn4zRUZ2gXRNX2tOwKTJW__n5X3-4LpY40CahIXrqtbHJrnAm3v94apzaRj1Uz_X3jLc6B2rdbyHVfqoiwX6U7nEzwCzy96KZFfY-Ey/w640-h418/deploy-to-azure-workflow-yml.png" width="640" /></a></div><br /><p>由上圖可知,我用來部署的原始碼分支是 demo,而此分支的根目錄底下有一個子目錄名稱也叫做 demo。專案的 .csproj 檔案所在位置是 /demo/BlazorApp1.Client 目錄。</p><blockquote><p>分支名稱和根目錄都叫做 "demo" 可能令人混淆(我當初命名時有點隨便)。比較常見的目錄結構會是 /src/BlazorApp1.Client 或者 /src/BlazorApp1/BlazorApp1.Client。</p></blockquote><p><br /></p><p>此外,部署至 Azure Static Web Apps 同樣會出現本文提及的「修正之三:用 index.html 取代 404 頁面」一節中描述的問題,但解法不同。我的作法是在專案的 wwwroot 目錄下增加一個檔案:<b>staticwebapp.config.json</b>。檔案內容如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBvBjGoiC6ULIIZjj3vocwI_xOh9OEAuzhYG2z7N5qOVvS5cPLSif2sr56RoRL-c7ULZ2O1HyIK9c7V75zgUjn2B9nT29H5-hiBVQXdcmuW67Nrl9TNmi8WSoTJOj68vAQLyIuHs7rydjLOk1p_pGeUMeYhHabTmH-k_Fr5xgWeJCeevVQ7mWktm0m/s648/fix-azure-static-web-404.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="282" data-original-width="648" height="279" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBvBjGoiC6ULIIZjj3vocwI_xOh9OEAuzhYG2z7N5qOVvS5cPLSif2sr56RoRL-c7ULZ2O1HyIK9c7V75zgUjn2B9nT29H5-hiBVQXdcmuW67Nrl9TNmi8WSoTJOj68vAQLyIuHs7rydjLOk1p_pGeUMeYhHabTmH-k_Fr5xgWeJCeevVQ7mWktm0m/w640-h279/fix-azure-static-web-404.png" width="640" /></a></div><br /><p>詳情可參考微軟文章:〈<a href="https://learn.microsoft.com/zh-tw/azure/static-web-apps/configuration#fallback-routes" target="_blank">設定 Azure Static Web Apps</a>〉。</p><p><br /></p><p>👉 <a href="https://agreeable-meadow-0ae92f300.2.azurestaticapps.net/" target="_blank">點此連結可查看我部署至 Azure Static Web Apps 的網站</a></p><h2 style="text-align: left;">下一步</h2><p>完成本文的相關操作之後,當你想到任何點子,只要是靜態網頁類型的 Blazor WebAssembly 應用程式,應該都能立刻著手進行開發,並且部署到 GitHub Pages 看看網頁實際執行起來的樣子。</p><p><br /></p><p>Blazor 還有許多有趣的東西可以玩。一邊學習,一邊把學到的東西拿來加強既有的應用程式,然後測試、部署,如此反覆循環,說不定一個有趣的工具或產品就這樣生出來了。</p><p><br /></p><p>Happy coding!</p>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-72949000524589462382022-12-16T23:22:00.012+08:002022-12-22T13:15:04.293+08:00 .NET Blazor 筆記:拆解專案結構、自訂版面配置<p>我的 .NET Blazor 筆記第二篇。本集內容依然是基礎知識,主要是講解 Blazor 專案範本的專案結構,順便介紹 Blazor 的版面配置(layout)。</p><span><a name='more'></a></span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxjRIyej5B4dsrSON4GwMcR96RhGbufGnpRpVMbYXknpeU2FTX6abKF25C7YRRNVcW3BHxmztwTxaH3B-BPNBOBaPgq9AGS1KEqjiGiuCBFvTeU-FpRjGTg5g6iQ5KpDyxyIGQzBshIQP0YbbmrASDWHf3FKRur5lIYQGgJTNF8les_kl532Iys_kn/s617/article-banner-2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="452" data-original-width="617" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxjRIyej5B4dsrSON4GwMcR96RhGbufGnpRpVMbYXknpeU2FTX6abKF25C7YRRNVcW3BHxmztwTxaH3B-BPNBOBaPgq9AGS1KEqjiGiuCBFvTeU-FpRjGTg5g6iQ5KpDyxyIGQzBshIQP0YbbmrASDWHf3FKRur5lIYQGgJTNF8les_kl532Iys_kn/s16000/article-banner-2.png" /></a></div><h2>如何使用這個筆記</h2><div>按目前的想法,我的 Blazor 筆記頂多只會觸及一些基本功。如果你正打算開始學習 Blazor,這些筆記或許有點幫助。我建議碰到需要動手寫程式的時候就照著範例寫一遍;如果發現我沒寫清楚的地方,就查 google 或閱讀 learn.microsoft.com 的<a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/" target="_blank">技術文件</a>與<a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/tutorials/" target="_blank">教學課程</a>。</div><div><br /></div><div><div>底下是本系列各篇筆記的連結:</div><div><ol style="text-align: left;"><li><a href="https://www.huanlintalk.com/2022/12/net-blazor-introduction.html" target="_blank">.NET Blazor 筆記:簡介</a></li><li>.NET Blazor 筆記:拆解專案結構、自訂版面配置 (你目前所在位置)</li><li><a href="https://www.huanlintalk.com/2022/12/deploying-blazor-app-to-github-pages.html" target="_blank">部署 Blazor WebAssembly app 至 GitHub Pages</a></li><li><a href="https://www.huanlintalk.com/2022/12/getting-started-with-mudblazor.html" target="_blank">MudBlazor 快速上手</a></li></ol></div></div><div><br /></div><div>相關參考資料已經寫在第一集〈<a href="https://www.huanlintalk.com/2022/12/net-blazor-introduction.html" target="_blank">.NET Blazor 筆記:簡介</a>〉,這裡就不再重複。</div><div><br /></div><div><b>開發工具與版本:</b>.NET 7 SDK,Visual Studio 2022 17.5.0</div><div><br /></div>
<hr />
<div><b>內容大綱:</b></div><div><ul style="text-align: left;"><li>你的第一個 Blazor App</li><ul><li>專案範本</li><li>建立專案(Youtube 影片)</li></ul><li>拆解專案結構</li><ul><li>程式進入點:Program.cs (以及<span style="color: #990000;"><b>依賴注入</b>的三種生命週期</span>)</li><li>起始頁面:index.html</li><li>根元件:App.razor</li><li>版面配置:MainLayout.razor</li></ul><li>練習:自訂版面配置(Youtube 影片)</li><li>下一步</li></ul></div><div>
<hr />
<div><br /></div><h2 style="text-align: left;">你的第一個 Blazor App</h2><div>「又是 Hello World 等級的 Blazor 程式,很無聊耶!」</div><div><br /></div><div>我懂,但是從初學入門的角度來看,第一個範例程式還是越簡單越好。更何況,Blazor 專案範本所產生的基本程式碼,其中有一些「機關」是初學者需要花點時間去了解的。</div><div><br /></div><div>重點不是建立新專案的操作步驟,而是稍後拆解專案結構的部分。</div><h3 style="text-align: left;">專案範本</h3><div>Visual Studio 為 Blazor app 提供了兩個專案範本:Blazor Server App 和 Blazor WebAssembly App,分別對應至 Blazor 的兩種裝載模式(<a href="https://www.huanlintalk.com/2022/12/net-blazor-introduction.html" target="_blank">上一篇筆記</a>提過)。透過這兩個範本所建立的專案,它們的檔案目錄結構以及 .csproj 檔案內容會有一些差異。</div><div><br /></div><div>首先,當你選擇使用 Blazor Server App 範本來建立專案,那麼整個 solution 裡面就只會有一個專案,而且專案檔(.csproj)只有短短幾行的內容:</div></div><div><br /></div>
<div>
<pre class="notranslate" style="background-color: #eff0f1; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project></code></pre>
</div>
<div>如果是 Blazor WebAssembly App 範本,你會在建立專案的過程中看到一些額外的選項,如下圖:</div><div><br /></div><div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsoEvFEeIxSwslPP7BjhZbCKhsRLvEKKcSCjvZH4XPzP42FgThmuKf5V0QVdkwCE1aEEf1rULjBvMUTh2sqbTc-92p-jpSgfPQarrDsNDlmwocj9_nhhWcB7z6X1Lpbs1T0Kzez8CdvnR3aDkHVooNMFuQuIQmKG5Kq-dMpt7RyKbP79YDJNLtB7i-/s796/new-project-options.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="455" data-original-width="796" height="366" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsoEvFEeIxSwslPP7BjhZbCKhsRLvEKKcSCjvZH4XPzP42FgThmuKf5V0QVdkwCE1aEEf1rULjBvMUTh2sqbTc-92p-jpSgfPQarrDsNDlmwocj9_nhhWcB7z6X1Lpbs1T0Kzez8CdvnR3aDkHVooNMFuQuIQmKG5Kq-dMpt7RyKbP79YDJNLtB7i-/w640-h366/new-project-options.png" width="640" /></a></div><div><br /></div><div>在不修改任何預設選項的情況下所建立之專案,即所謂的 standalone Blazor WebAssembly app,專案建立完成後,solution 裡面只有一個專案。專案檔內容如下:</div><div><br /></div><div><pre class="notranslate" style="background-color: #eff0f1; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.0" PrivateAssets="all" />
</ItemGroup>
</Project></code></pre></div><div><br /></div><div><div>其中只有兩個 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><PackageReference></span> 標籤,指明了專案需要用到的兩個 NuGet 套件。這裡並不需要列出專案用到的全部套件,而只要寫最上層的套件就行了,因為每個套件所用到的其他相依套件都會在建置專案的過程中自動加入。</div><div><br /></div><div>下圖同時呈現 Blazor Server app(左邊)和 Blazor WebAssembly app(右邊)的專案檔內容,以便看出二者差異:</div></div><div><br /></div><div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBatVHkmyIuB_u67eN4YxkDkz5lxEl5YvQdtszbjLAHuhiQowrnqW0Gu_kxj3n52IeYrskjtE4dPaB41sBxqF6rjoCI5KPZbm1zEhkwndlLADSc9gB--aQtakSynvAzpuYuxtD0TXNNN71RI26wlskjBWKcWRjS98XJPZKeHQiZ9IT_t2o8nYJyus_/s1101/blazor-csproj-diff.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="456" data-original-width="1101" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBatVHkmyIuB_u67eN4YxkDkz5lxEl5YvQdtszbjLAHuhiQowrnqW0Gu_kxj3n52IeYrskjtE4dPaB41sBxqF6rjoCI5KPZbm1zEhkwndlLADSc9gB--aQtakSynvAzpuYuxtD0TXNNN71RI26wlskjBWKcWRjS98XJPZKeHQiZ9IT_t2o8nYJyus_/w640-h266/blazor-csproj-diff.png" width="640" /></a></div><div><blockquote><div>隨著 .NET 版本演進,你在實際操作時所看到的 csproj 檔案內容可能跟上圖不同。</div></blockquote><div><br /></div><div>建立新專案時,如果勾選了「ASP.NET Core Hosted」(參見本節稍早截圖),則表示你打算讓後端主機也提供 ASP.NET 執行環境,此時你所建立的應用程式就是所謂的 hosted Blazor WebAssembly app,而 solution 裡面至少會有三個專案,分別是:</div><div><ul style="text-align: left;"><li>用戶端專案,即 Blazor WebAssembly app。</li><li>伺服器專案,即 ASP.NET Core app。</li><li>共用程式庫,即一般的 .NET 類別庫。</li></ul></div><div><br /></div><div>參考下圖:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjA1gbdpnVdcjT_PyGQYH_1YeBSAhCY1D-2BETMpvuhJcZdohEXNvFN_TbfqGSmG33LYF-p8eEzy7mKPerbQG8DvYaeRuULIqE7aW5AFqJKV8fcolGbsS5-IADhMvW0QGq_O8nvj0kCj5PZZPR251xx_8LE3MRcFw8uIWu2g8J75fpAduGRPUF4bjZK/s454/hosted-blazor-wasm-projects.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="253" data-original-width="454" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjA1gbdpnVdcjT_PyGQYH_1YeBSAhCY1D-2BETMpvuhJcZdohEXNvFN_TbfqGSmG33LYF-p8eEzy7mKPerbQG8DvYaeRuULIqE7aW5AFqJKV8fcolGbsS5-IADhMvW0QGq_O8nvj0kCj5PZZPR251xx_8LE3MRcFw8uIWu2g8J75fpAduGRPUF4bjZK/s16000/hosted-blazor-wasm-projects.png" /></a></div><div></div><div><br /></div><div><div>採用此配置,即表示你要建立全端(full-stack).NET 應用程式,亦即後端主機可使用完整的 ASP.NET Core API。</div><div><br /></div><div>如前面提過的,這種 hosted Blazor WebAssembly app 由於需要在後端主機上面安裝 .NET 執行環境,故此類應用程式將無法部署於 GitHub Pages 之類的靜態網頁伺服器。</div><h3 style="text-align: left;">建立專案</h3><div>了解 Blazor 專案範本與相關選項之後,接著就來動手建立第一個 Blazor WebAssembly app。</div><div><br /></div><div>往後示範的操作都是用 Visual Studio 2022。你也可以使用 .NET CLI(命令列)或其他 IDE,例如 Visual Studio Code 或 JetBrains Rider。</div><div><br /></div><div>建立專案的操作步驟可參考我的 Youtube 影片(建議<a href="https://www.youtube.com/watch?v=0kUT79Hsg6I" target="_blank">至 Youtube 網站觀看</a>):</div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="314" src="https://www.youtube.com/embed/0kUT79Hsg6I" width="378" youtube-src-id="0kUT79Hsg6I"></iframe></div><br /><div><div><br /></div><div>如果你偏好使用命令列,可參考以下指令:</div><div><pre class="notranslate" style="background-color: white; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background: transparent; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">dotnet new blazorwasm -o BlazorApp1/BlazorApp1.Client
dotnet new sln -n BlazorApp1
dotnet sln add BlazorApp1\BlazorApp1.Client</code></pre></div><div>我在專案名稱後面加上「.Client」,以明確表示此專案是執行於用戶端瀏覽器的 Blazor WebAssembly app。此外,將來如果發現有需要在後端主機加入 ASP.NET Core 應用程式,則可以將伺服器專案命名為 BlazorApp1.Server。</div><div><br /></div><h2 style="text-align: left;">拆解專案結構</h2><div>在閱讀本節內容時,如果遇到不明白的地方,建議搭配微軟網站上的這篇文章一起看:<a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/project-structure" target="_blank">ASP.NET Core Blazor 專案結構</a>,說不定就能豁然開朗。</div><div><br /></div><div>這裡要解說的是以下幾個重要部分:</div><div><ul style="text-align: left;"><li>程式進入點:Program.cs</li><li>起始頁面:index.html</li><li>根元件:App.razor</li><li>版面配置:MainLayout.razor</li></ul></div><h3 style="text-align: left;">程式進入點:Program.cs</h3><div>Program.cs 位於專案的根目錄,此檔案包含了應用程式的進入點,也就是 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Main</span> 方法。</div><div><br /></div><div>預設情況下,Blazor 專案範本所產生的 Program.cs 會採用「<a href="https://learn.microsoft.com/zh-tw/dotnet/csharp/fundamentals/program-structure/top-level-statements" target="_blank">最上層語句</a>」的寫法(C# 9 開始提供),所以我們不會在裡面看到明確宣告的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Program</span> 類別和 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Main</span> 方法。閱讀程式碼的時候,只要知道編譯器會自動把這個檔案的程式碼編譯成 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Main</span> 方法就行了。程式碼如下:</div></div><div><br /></div>
<script src="https://gist.github.com/huanlin/949d79abf872fab0206bddba3f2cc4af.js"></script>
<div><br /></div>
<div>說明:</div><div><div><ul style="text-align: left;"><li>第 5 行:建立 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">WebAssemblyHostBuilder</span> 物件。</li><li>第 6 行:加入應用程式的根元件:<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">App</span>。這裡呼叫的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">builder.RootComponents.Add</span> 方法,其傳入參數是一個 CSS 選擇器,而 "#app" 即表示要尋找 ID 為 "app" 的元素——至於這個元素在哪裡,稍後會加以說明。</li><li>第 7 行:再加入一個根元件:<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">HeadOutlet</span>。目的是讓我們能修改首頁的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">head</span> 元素的內容。</li><li>第 9 行:註冊應用程式所需之服務。這裡是把一個 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">HttpClient</span> 物件加入服務集合。</li><li>第 11 行:根據 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">WebAssemblyHostBuilder</span> 的設定來建立 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">WebAssemblyHost</span> 物件,並呼叫其 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">RunAsync</span> 方法來運行應用程式。</li></ul></div><div><br /></div><div>第 9 行使用了所謂的 <b>DI</b>(dependency injection;<b>依賴注入</b>)模式來註冊應用程式所需要的服務。註冊服務至 DI 容器的方法有 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">AddTransient</span>、<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">AddScoped</span> 和 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">AddSingleton</span>,分別對應至以下三種生命週期:</div><div><ul style="text-align: left;"><li><b>Transient</b>:每當外界向 DI 容器索取某服務的執行個體,容器就會提供一個全新建立的執行個體。</li><li><b>Scoped</b>:在同一個 HTTP 請求的範圍內只會建立一個執行個體。換言之,在同一個 HTTP 請求的範圍內共用某服務的執行個體。</li><li><b>Singleton</b>:DI 容器只會替指定之服務建立一個執行個體,並由整個應用程式共用。也就是說,同時進行中的多個 HTTP 請求都會使用同一個執行個體。</li></ul></div><div><br /></div><div>比較特別的是,在 Blazor WebAssembly app 中,Scoped 和 Singleton 二者的作用是一樣的。這是因為 Blazor WebAssembly app 在跳轉頁面的時候不會發出 HTTP 請求,故任何 Scoped 物件一旦建立之後便會跟 Singleton 物件一樣持續存在,直到應用程式結束才會消失。</div><div><br /></div>
<div>接著要看的是應用程式進入點之後所載入的初始頁面:index.html。</div></div><div><h3 style="text-align: left;">起始頁面:index.html</h3><div>位於 wwwroot 目錄下的 index.html 是以標準 HTML 來實作的應用程式起始頁面。下圖標示了此檔案的幾個關鍵內容。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDlI8JPRKkP0dSoCbLY8IsCG0HYI4IOXDRzTJ92gSid-Guzvg27vwfvQekp5QAW58s3UkiY5SKszrUkE4Af573RczgOjVcrj1KDEHQiJzCU5T9dVYdzwhtpGuLn9CRgAdZNTfNhFur_ZcbKOFx69RwveIBxyLyflmDUfdN_KYT5UwaCUqON7XYXiUV/s1114/index-html.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="664" data-original-width="1114" height="381" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDlI8JPRKkP0dSoCbLY8IsCG0HYI4IOXDRzTJ92gSid-Guzvg27vwfvQekp5QAW58s3UkiY5SKszrUkE4Af573RczgOjVcrj1KDEHQiJzCU5T9dVYdzwhtpGuLn9CRgAdZNTfNhFur_ZcbKOFx69RwveIBxyLyflmDUfdN_KYT5UwaCUqON7XYXiUV/w640-h381/index-html.png" width="640" /></a></div><br /><div><br /></div><div>位於 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><head></span> 內的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><base></span> 標籤很重要,如果設定錯誤,Blazor 將無法正確處理用戶端頁面的跳轉。預設情況下,Index.html 檔案的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace"><base></span></span> 標籤的屬性值是 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">/</span>,也就是網域的根,例如 www.huanlintalk.com。如果應用程式的部署路徑是網域之下的子路徑,例如 www.huanlintalk.com/books,那麼 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><base></span> 標籤的屬性值就必須設定為 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">/books/</span>。請注意最後的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">/</span> 字元是必須的;如果少了最後的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">/</span>,瀏覽器會忽略任何字元,直到它找到 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">/</span>。比如說,<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">/books</span> 會被當作 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">/</span>。</div><div><br /></div><div>位於 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><body></span> 標籤內的第一個 div 元素,ID 是 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">app</span>,當應用程式執行時,Blazor 會找到此 div 元素,並將其內容置換成 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">App</span> 元件所呈現的內容。</div><div><br /></div><div>那麼,什麼是 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">App</span> 元件?它又扮演了什麼角色呢?請接著看下一節的說明。</div><h3 style="text-align: left;">根元件:App.razor</h3><div>剛才說的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">App</span> 元件,就是位於專案根目錄下的 App.razor,它是 Blazor 應用程式的根元件。底下是由 Blazor 專案範本所產生的 App.razor 檔案內容:</div><div><br /></div>
<script src="https://gist.github.com/huanlin/7884c05023ff6e3aa418430dd9e7a671.js"></script>
<div><br /></div>
<div>這個 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">App</span> 元件裡面包含一個名為 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Router</span> 的元件,即上列程式碼最上層的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><Router></span> 標籤。<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #57606a; font-size: 13.6px;">Router</span> 元件的責任是管理用戶端路由(routing)的相關工作,故它在多頁面的應用程式中扮演了非常重要的角色。</div><div><br /></div>
<div>當應用程式啟動時,<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #57606a; font-size: 13.6px;">App</span> 元件裡面的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Router</span> 元件會自動掃描應用程式的相關組件,以找出所有的可路由元件(routable components),並將相關資訊儲存於一個路由表。每當使用者在頁面上點擊某個連結,或程式某個地方觸發了導覽事件,<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Router</span> 就會試著在路由表中尋找符合當前請求的路由。若有找到目前請求的路由,便會將目標頁面的內容呈現於 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><Found></span> 區塊;如果找不到符合的路由,便會顯示 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><NotFound></span> 區塊所定義的頁面內容。</div><div><br /></div><blockquote><div>重點整理:<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #57606a; font-size: 13.6px;">App</span> 元件是透過 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Router</span> 元件來處理用戶端的所有頁面跳轉工作。</div></blockquote><div><br /></div>
<div>舉例來說,瀏覽器初次載入此應用程式的時候,欲存取的路徑是 "/",所以 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Router</span> 會尋找有 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@page "/"</span> 指示詞的元件(即可路由元件,或者可跳轉頁面)。在專案範本所產生的程式碼當中,符合 "/" 路徑的元件就是位於 Page 目錄下的 Index.razor。底下為專案範本所產生的程式碼:</div>
<div><br /></div>
<script src="https://gist.github.com/huanlin/cbb616bc5a38a098b7cbb346f6431cf9.js"></script>
<div><br /></div>
<div>程式執行結果如下圖:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKXnix_8QOm940W8jXWkcZSi5rF6gtGHyCNY8zsk6s1a08Q4PJubSlmTQ4UIEIGJ-V_1pJDid8EtJw6r2GyEeMb3lEVoPSm-MMnVlHIxO9YrkWf4hkbK_kmFVxJN76x60-mMJUL68RbgNRPdRl9__4iqmk9oF7KUyKMUYu9kzyX-9lbuYhi6JnMsYD/s936/index-page.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="422" data-original-width="936" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKXnix_8QOm940W8jXWkcZSi5rF6gtGHyCNY8zsk6s1a08Q4PJubSlmTQ4UIEIGJ-V_1pJDid8EtJw6r2GyEeMb3lEVoPSm-MMnVlHIxO9YrkWf4hkbK_kmFVxJN76x60-mMJUL68RbgNRPdRl9__4iqmk9oF7KUyKMUYu9kzyX-9lbuYhi6JnMsYD/w640-h288/index-page.png" width="640" /></a></div><br /><div>咦,可是 Index.razor 的原始碼裡面並沒有左側選單,也沒有頁面頂端的應用程式標題和 About 連結。而且,無論使用者點擊左邊選單的 Home、Counter、還是 Fetch data 連結,上方標題列和左邊選單的內容都不會變,只有選單右邊的區塊會隨著使用者點擊的連結而改變。這是怎麼做到的呢?</div><div><br /></div><div>這就要提到 Blazor 的版面配置了。</div><h3 style="text-align: left;">版面配置:MainLayout.razor</h3><div>使用者看到的完整頁面,是由剛才提過的 App.razor 當中的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Router</span> 元件所定義的版面配置(layout)來決定,也就是 App.razor 當中的這行程式碼:</div><div><pre class="notranslate" style="border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /></code></pre></div><div>其中的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">DefaultLayout</span> 屬性代表預設的版面配置元件類型,這裡的設定是 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">MainLayout</span>,而這個 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">MainLayout</span> 元件就是專案根目錄下的 Shared 子目錄中的 MainLayout.razor。</div><div><br /></div><div>底下是 Blazor 專案範本所產生的 MainLayout.razor 原始碼:</div>
<div><br /></div>
<script src="https://gist.github.com/huanlin/488bf4955dcefb9d6a1daf68f23abada.js"></script>
<div><br /></div>
<div>說明:</div><div><ul style="text-align: left;"><li>第 1 行:所有的版面配置元件都必須繼承自 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">LayoutComponentBase</span>。</li><li>第 5 行:<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><NavMenu></span> 元件負責呈現導覽區的內容,其中包含一個選單。此元件的檔案位於 Shared 資料夾,檔名是 NavMenu.razor。</li><li><span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><main></span> 元素裡面有兩個區塊,首先是頁面頂端標題區的 About 連結,接著是一個 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;"><article></span> 元素,裡面的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@Body</span> 就是用來置換使用者每次點擊連結時所顯示的頁面內容。比如說,當你在左邊導覽區點了 Counter 連結,那麼 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@Body</span> 就會被置換成 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Counter</span> 元件的轉譯結果。</li></ul></div><div><br /></div><div>所有套用此版面配置的頁面,其最終呈現於瀏覽器的樣貌如下圖:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhATP6MZYpb33_6E3KW0xdN_q8wX6Zu7vERefNPQS8eCESh0kpMg4QbL6smkrLVhiagKOFBWiTgpLTPRGthfcG_DUotu6rt4vBtaP6savaeIqDo6oy0qyXRNXwTs9bZO7hctFUkZ_xYWhpjirOrgesI9Hnf08f4Mi9leXsxG-kuP13-nwqRRBMBLZBi/s613/blazor-default-layout.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="374" data-original-width="613" height="390" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhATP6MZYpb33_6E3KW0xdN_q8wX6Zu7vERefNPQS8eCESh0kpMg4QbL6smkrLVhiagKOFBWiTgpLTPRGthfcG_DUotu6rt4vBtaP6savaeIqDo6oy0qyXRNXwTs9bZO7hctFUkZ_xYWhpjirOrgesI9Hnf08f4Mi9leXsxG-kuP13-nwqRRBMBLZBi/w640-h390/blazor-default-layout.drawio.png" width="640" /></a></div><br /><div>除了剛才介紹的預設版面配置,你也可以自行設計其他版面,並於頁面中使用 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@layout</span> 指示詞來套用不同的版面配置。(稍後有教學影片)</div><h3 style="text-align: left;">wwwroot 資料夾與 _Imports.razor</h3><div>ASP.NET Core 應用程式都會有一個 wwwroot 資料夾,裡面存放的是用戶端能夠下載的靜態檔案,例如 HTML、CSS、圖片、JavaScript 等等。</div><div><br /></div><div>至於 _Imports.razor,這個檔案雖然不是絕對必要,但它可以讓程式碼更簡潔。我們通常會在這個檔案裡面放一些指示詞,例如 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@using</span>、<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@layout</span>等等。底下是範例:</div><div><br /></div>
<div>
<pre class="notranslate" style="background-color: #eff0f1; border-radius: 6px; box-sizing: border-box; color: #24292f; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp1
@using BlazorApp1.Shared</code></pre></div><div>只要把常用的命名空間的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@using</span> 指示詞寫在 _Imports.razor 檔案裡,這個檔案的所在目錄以及底下的所有子目錄的程式檔案便會自動引用這些命名空間。如此一來,寫程式的時候就不用一直重複引用這些常用的命名空間。</div><div><br /></div><div>_Imports.razor 檔案不一定要放在專案的根目錄,而且可以不只一個。如果某些命名空間是特定資料夾的程式碼才會用到,例如在專案根目錄底下的 Infrastructure\Security 子目錄,我們也可以在這裡建一個 _Imports.razor 檔案,讓檔案中引用的命名空間自動套用至 Security 目錄以及其下所有子目錄的程式碼。</div></div><h2 style="text-align: left;">練習:自訂版面配置</h2><div>大致了解前面介紹的幾個重要部分之後,不妨動手修改原先建立的第一個 Blazor app,嘗試建立新的版面配置並套用至特定頁面。</div><div><br /></div><div>可以參考這個教學影片來練習(建議<a href="https://youtu.be/nIZDxMj2iNw" target="_blank">至 Youtube 網站觀看</a>):</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="353" src="https://www.youtube.com/embed/nIZDxMj2iNw" width="425" youtube-src-id="nIZDxMj2iNw"></iframe></div><div><br /></div><h2 style="text-align: left;">下一步</h2><div>雖然 Blazor 還有很多東西可以講,但我打算在下一篇筆記就先寫一點與「部署」有關的操作。也就是說,只要按照順序完成這個 Blazor 入門筆記系列的前三篇文章所涵蓋的課題和練習,就能寫出一個最簡單的 Blazor WebAssembly app,並且部署到免費的靜態網站主機(例如 GitHub Pages)。</div><div><br /></div><div>Keep coding!</div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-70026880815808514282022-12-10T23:36:00.016+08:002022-12-22T13:14:52.755+08:00.NET Blazor 筆記:簡介<p>我的 .NET Blazor 筆記,第 1 篇。</p><span><a name='more'></a></span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW6wLIl7SHoC_yeB-Vp_Wc9_MM66Tfs1JgOtKZBxBsG_jZjnKCml-cogeMODTXtGPuojdPKXg4I5VI7rkmt-i6VvaATb0qojawPpG1byTxBxFNNHRgc8oOILJBtVjvR6GHiijdmJW5PRErkNbSweAEXbcHUUE8vwcul4xkBoFOBy7RHjeAc4gU3DNr/s617/article-banner.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="452" data-original-width="617" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW6wLIl7SHoC_yeB-Vp_Wc9_MM66Tfs1JgOtKZBxBsG_jZjnKCml-cogeMODTXtGPuojdPKXg4I5VI7rkmt-i6VvaATb0qojawPpG1byTxBxFNNHRgc8oOILJBtVjvR6GHiijdmJW5PRErkNbSweAEXbcHUUE8vwcul4xkBoFOBy7RHjeAc4gU3DNr/s16000/article-banner.png" /></a></div><h2 style="text-align: left;">如何使用這個筆記</h2><div>按目前的想法,我的 Blazor 筆記頂多只會觸及一些基本功。如果你正打算開始學習 Blazor,這些筆記或許有點幫助。我建議碰到需要動手寫程式的時候就照著範例寫一遍;如果發現我沒寫清楚的地方,就查 google 或閱讀 learn.microsoft.com 的<a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/" target="_blank">技術文件</a>與<a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/tutorials/" target="_blank">教學課程</a>。</div><div><br /></div><div>底下是本系列各篇筆記的連結:</div><div><ol style="text-align: left;"><li>簡介(你目前所在位置)</li><li><a href="https://www.huanlintalk.com/2022/12/net-blazor-your-first-app-with-custom.html" target="_blank">拆解專案結構、自訂版面配置</a></li><li><a href="https://www.huanlintalk.com/2022/12/deploying-blazor-app-to-github-pages.html" target="_blank">部署 Blazor WebAssembly app 至 GitHub Pages</a></li><li><a href="https://www.huanlintalk.com/2022/12/getting-started-with-mudblazor.html" target="_blank">MudBlazor 快速上手</a></li></ol></div><div><br /></div><div>進入正題之前,先把我這份學習筆記的主要參考資料列出來,後面續集就不再重複列出這些參考資料:</div><div><ul style="text-align: left;"><li><a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/" target="_blank">微軟文件:ASP.NET Core Blazor</a></li><li><a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/tutorials/?" target="_blank">微軟課程:ASP.NET Core Blazor 教學課程</a></li><li><a href="https://www.manning.com/books/blazor-in-action" target="_blank">Blazor in Action</a> by Chris Sainty. Manning 2022.</li><li><a href="https://learn.microsoft.com/en-us/dotnet/architecture/blazor-for-web-forms-developers/" target="_blank">Blazor for ASP.NET Web Forms Developers. Microsoft 2022</a>. (電子書,免費下載)</li><li><a href="https://www.amazon.com/Microsoft-Blazor-Building-Applications-Beyond/dp/1484278445" target="_blank">Microsoft Blazor: Building Web Applications in .NET 6 and Beyond</a> 3rd edition by Peter Himschoot. Apress 2022.</li><li>部落格文章與 Youtube 影片,無法逐一羅列。</li></ul></div><blockquote><div>上列文件和書籍,我當然是沒有全部看完,只是撿我需要的看。</div></blockquote><div><br /></div>
<hr />
<div><b>內容大綱:</b></div><div><ul style="text-align: left;"><li>為何選擇 Blazor?</li><li>基於元件的 UI 框架</li><li>Blazor 的裝載模型</li><li>重點回顧(附中英術語對照表)</li></ul></div><div><hr /><div><br /></div><div>一直以來,儘管有 ASP.NET MVC、Razor Pages、Web API 等技術可用來開發 web 應用程式,但我們始終無法使用 C# 來撰寫用戶端的網頁程式——這是由 JavaScript 支配的領域,直到 Blazor 技術的出現,終於開始突破了這層限制。</div><div><br /></div><div>Blazor 是基於 web 標準規範的用戶端框架,可讓我們用 C# 與 .NET 來開發 web 應用程式,並提高生產力。</div><div><br /></div><div>Blazor 程式的運行方式,與其說是請求—回應模型,倒不如說它是事件驅動模型,更為貼切。</div></div><div><br /></div><h2 style="text-align: left;">為何選擇 Blazor?</h2><div><div>正所謂「萬事起頭難」,這句話用來形容一個 web 應用程式的誕生並不為過。尤其是這些年來,網頁前端技術百花齊放,令新手眼花撩亂,無所適從。比如說,在前端網頁框架有 Angular、React、Vue.js……等等,程式語言有 TypeScript、CoffeeScript、Dart,建構方面又有 webpack、Parcel、Browserify 等工具可供選擇。對一個 web 開發經驗不多的個人或團隊來說,逐一嘗試每一種選項幾乎是不可能的任務。即便是經驗豐富的開發人員,在不斷推陳出新的技術洪流中,也難免陷入選擇困難。</div></div><div><br /></div><div>那麼,Blazor 的出現能解決上述問題嗎?讓我們先來看看 Blazor 有哪些優點:</div><div><div><ul style="text-align: left;"><li><b>使用 C# 語言</b>:從前端到後端,.NET 開發人員都可以用自己熟悉的 C# 語言。</li><li><b>強大的開發工具</b>:Visual Studio 以及許多擴充工具(插件)。如果偏好輕量級的整合環境開發工具(IDE),也可以選擇 Visual Studio Code(簡稱 VS Code)。Visual Studio 可執行於 Windows 和 macOS 作業環境,VS Code 則同時支援 Windows、macOS、和 Linux。另外還有功能強大的第三方 IDE 可以選擇,例如 JetBrain 公司出品的 Rider(支援三種作業平台)。</li><li><b>豐富的 .NET 生態</b>:各式各樣現成而且免費的 NuGet 套件與程式庫。</li><li><b>自由彈性</b>:你可以自由選擇偏好的程式設計模式與框架,例如 MVVM(model-view-viewmodel)、Redux。</li><li><b>平緩的學習曲線</b>:對熟悉 .NET 技術的人來說,學習曲線相對平緩,因為 C#、Razor、依賴注入(dependency injection)等技術或程式寫法都比較熟悉,或者很容易就能上手。這也意味著開發人員將能縮短學習時間,更快投入應用程式的開發工作。</li><li><b>前端與後端程式碼共用</b>:開發 web 應用程式經常碰到的麻煩,是前端與後端資料模型(model 或 data transfer object;DTO)的重複。也就是說,同一個資料模型往往需要在後端和前端的程式碼各寫一份(C# 和 JavaScript),若稍後修改了資料模型,就必須同時修改兩份程式碼,以確保前後端資料模型的一致。在 Blazor,資料模型可以用 C# 撰寫,讓前端與後端共用同一份程式碼。</li><li><b>開放原始碼</b>:如同微軟的其他專案,Blazor 也是完全開放原始碼。</li></ul></div><div><br /></div><div>對許多人來說,Blazor 的最大好處可能是讓他們免於學習 Angular、Vue.js、和 React 等 JavaScript 框架,並且更容易開發跨平台的應用程式。Blazor 甚至可以搭配 MAUI——即 Blazor Hybrid 技術——來開發各種類型的跨平台應用程式,包括 web app、桌上型 app、行動裝置 app。</div><blockquote><div>💬 Blazor Hybrid 技術是透過 BlazorWebView 控制項來呈現 Blazor app 的內容。WinForms 和 WPF 也有提供 BlazorWebView。</div></blockquote><h2 style="text-align: left;">基於元件的 UI 框架</h2><div>在 Blazor 應用程式中,使用者看到的任何東西都是由元件所產生,包括網頁、網頁中的部分區塊、版面配置(layout)等等。</div><div><br /></div><div>每一個 Blazor app 都有一個或多個根元件(root component),這些元件最終會以特定 HTML 元素呈現於網頁。每一個 Blazor 元件都是 .NET 類別,它們通常被設計成可重複使用的 UI 元素。每個元件負責管理自己的狀態並決定如何轉譯(render)成 HTML,而當使用者對元件進行特定操作時,便會觸發相應的事件來變更元件的狀態或外觀。</div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-igUczoEMeMFmi8bgaghCEC0APVNwVsIFlJMb_GgbM9w_4MH11vNs6QpDC8px0YQCUHHamn6ScMKWhXQfuhgM4xkkVE26RSPgZvpy_sZTYGq6V_k91dUod6oQy3gUqfPR1GhSJIy4-VM7NwLhbqXUcU7eixcUTSkvgBqL_PAPcQt8xwKu5dLLwE-b/s378/component-based-ui.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="378" data-original-width="340" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-igUczoEMeMFmi8bgaghCEC0APVNwVsIFlJMb_GgbM9w_4MH11vNs6QpDC8px0YQCUHHamn6ScMKWhXQfuhgM4xkkVE26RSPgZvpy_sZTYGq6V_k91dUod6oQy3gUqfPR1GhSJIy4-VM7NwLhbqXUcU7eixcUTSkvgBqL_PAPcQt8xwKu5dLLwE-b/s16000/component-based-ui.drawio.png" /></a></div><br /><div>當元件處理完某個事件之後,Blazor 會產生該元件的網頁內容,並記錄這次網頁內容的變動。元件本身並不會直接修改網頁的 DOM(Document Object Model),而是修改 DOM 的分身,叫做 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">RenderTree</span>。Blazor 會比對 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">RenderTree</span> 變更前後的內容,以找出 UI 的差異,並依此差異來修改 DOM。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggu9GaAv-f8XBVBpWIf_uwISBpkwFYOYnMl-0hPeEdW8_K9xkNXKfz3rED1szDymJgHwyZbL6G2mS7ctbc6YpjzDXXAbQZmS99xIlzS0TwR4n9lhbox-z1F4vRPpN4_YTkUk1U6qqOVsXDR76hk-vB4bo5FJMvGg-ByDq_IOgqtjQKXPnirN-7RwdG/s410/render-tree.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="410" data-original-width="331" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggu9GaAv-f8XBVBpWIf_uwISBpkwFYOYnMl-0hPeEdW8_K9xkNXKfz3rED1szDymJgHwyZbL6G2mS7ctbc6YpjzDXXAbQZmS99xIlzS0TwR4n9lhbox-z1F4vRPpN4_YTkUk1U6qqOVsXDR76hk-vB4bo5FJMvGg-ByDq_IOgqtjQKXPnirN-7RwdG/s16000/render-tree.png" /></a></div><br /><div>相較於每次重新產生整個網頁的內容,Blazor 針對有需要更新的部分來修改 DOM,是更有效率的做法。</div><div><br /></div><div><h3 style="text-align: left;">可路由元件</h3><div>在程式碼頂端有加上 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@page</span> 指示詞的元件叫做<b>可路由元件</b>(routable component)。基本上,可路由元件就是應用程式中的一個頁面。下圖是一個簡單範例:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxY9CwlB-dHTx2WnyFO3CWqTU1ZXL6pKn_3Mx76Z0hezvR5htTEhAaGpH1L59j2283tOXBb_9xblLzO1avd3_EJ76Zz7qfFGcICPi1w2n8CTGU1lQ24kyd0onHuiFzym6tJSdiaVW95gV0GABJN1bY8Sse4rouAbWnIvZT_OXSdiH5ya4cfUmBl6cl/s794/component-code-sections.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="424" data-original-width="794" height="342" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxY9CwlB-dHTx2WnyFO3CWqTU1ZXL6pKn_3Mx76Z0hezvR5htTEhAaGpH1L59j2283tOXBb_9xblLzO1avd3_EJ76Zz7qfFGcICPi1w2n8CTGU1lQ24kyd0onHuiFzym6tJSdiaVW95gV0GABJN1bY8Sse4rouAbWnIvZT_OXSdiH5ya4cfUmBl6cl/w640-h342/component-code-sections.png" width="640" /></a></div><br /><div><div>此範例程式碼包含三大區塊:</div><div><ol style="text-align: left;"><li>頂端區塊:指示詞,例如 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@page</span>、<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@using</span>、<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@inject</span> 等等。</li><li>中間區塊:視覺化元素的標記;這裡通常會混合使用 Razor、C#、和 HTML 三種語法。</li><li>底部區塊:C# 程式碼;你可以在這裡加入欄位、屬性,甚至整個類別。</li></ol><blockquote><div>💬 寫程式的時候如果需要查閱 Razor 語法,可參考微軟文章:<a href="https://learn.microsoft.com/zh-tw/aspnet/core/mvc/views/razor" target="_blank">ASP.NET Core 的 Razor 語法參考</a>。</div></blockquote></div></div></div><div><h2 style="text-align: left;">Blazor 裝載模型</h2><div>裝載模型(hosting model)是開發 Blazor app 時必須盡早了解的必要知識,因為它牽涉到你的 Blazor app 的部署方式,以及應用程式執行在什麼位置(前端或後端)。</div><div><br /></div><div>Blazor 應用程式有兩種裝載模型:</div><div><ul style="text-align: left;"><li>Blazor WebAssembly:簡稱 <b>Blazor WASM</b>,.NET 程式碼執行於用戶端瀏覽器。</li><li>Blazor Server:部署於後端主機,.NET 程式碼完全執行於後端機器。</li></ul></div><div><br /></div><div>不用太擔心是否會選錯裝載模型,因為 Blazor 的設計架構已經把應用程式與裝載模型分開,將來如果想要改用另一種裝載模型並不會太困難。</div></div><div><br /></div><div><h3 style="text-align: left;">Blazor WebAssembly app</h3><div>Blazor WebAssembly app 是以 WebAssembly 的形式運行於用戶端的瀏覽器,其功能類似前端 JavaScript 框架(例如 Angular、React),但不是以 JavaScript 撰寫,而是 C#。程式執行時所需要的 .NET 基礎程式庫與其他相關套件都會隨著應用程式一起下載至用戶端;這些套件通常是一般的 .NET 組件(assembly)。</div><blockquote><div>💬 .NET 6 提供了 AOT(ahead-of-time),即預先編譯的方式,讓開發人員能夠把應用程式編譯成 WebAssembly。好處是能夠大幅提升需要 CPU 運算的程式碼執行效能,缺點則是編譯出來的檔案比較大,約略比一般 .NET 組件大個兩倍左右,意味著用戶端下載檔案時必須等待更久的時間。</div></blockquote><div><br /></div><div>由於 WebAssembly app 的 .NET 執行環境支援 .NET Standard,所以只要是符合 .NET Standard 規格的程式庫皆可使用。不過,這些組件是執行於瀏覽器中,故其存取權限仍受限於瀏覽器。比如說,嘗試存取本機電腦的檔案或建立某些網路連線時,便可能因為違反安全規範而出現 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">PlatformNotSupportedException</span>。</div></div><div><h4 style="text-align: left;">啟動過程</h4><div>如下圖:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo5rKFhORcQ-XspshiQZJftWS3afJUZkMF349DTaTaYgJg3jGs4vPpuYffQ-fTt4A0d-Ef0qQxExDsscNeewu9pp5S8vzekjTtHkPnM8buC5nq2sRmzGqhRdar9SQNMr1rNNPiQjGtgZum3e7vsT12DuD0OmT2JdGasrS_WSZuj_HHaRA4Zeh2k3--/s709/blazor-wasm-app-boot-up.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="356" data-original-width="709" height="321" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo5rKFhORcQ-XspshiQZJftWS3afJUZkMF349DTaTaYgJg3jGs4vPpuYffQ-fTt4A0d-Ef0qQxExDsscNeewu9pp5S8vzekjTtHkPnM8buC5nq2sRmzGqhRdar9SQNMr1rNNPiQjGtgZum3e7vsT12DuD0OmT2JdGasrS_WSZuj_HHaRA4Zeh2k3--/w640-h321/blazor-wasm-app-boot-up.drawio.png" width="640" /></a></div><br /><div>首先,使用者在瀏覽器中開啟網站首頁,瀏覽器發送請求至後端主機,於是主機回傳一組檔案,包括首頁(通常是 index.html)、圖片、CSS、JavaScript 等等。其中包括一個很重要的 JavaScript 檔案:<b>blazor.webassembly.js</b>,它肩負以下工作:</div><div><ul style="text-align: left;"><li>在瀏覽器中載入並初始化 Blazor 應用程式。</li><li>直接操作 DOM 來更新網頁內容。</li><li>提供 API 來進行一些需要使用 JavaScript 的場合。</li></ul></div><div><br /></div><div>一旦瀏覽器下載好這些初始階段所需之檔案,便會建立目前頁面的 DOM。然後,blazor.webassembly.js 又會下載一個名為 <b>blazor.boot.json</b> 的檔案,其內容記錄著 Blazor 框架以及應用程式所需要的其他相關檔案。</div><div><br /></div><div>剛才提到的從主機下載至用戶端的檔案,它們全都是靜態檔案,也就是不需要在後端主機進行編譯或動態生成等操作,故後端主機甚至不需要安裝 .NET,而且 app 可以部署至任何支援靜態網頁的伺服器,例如 GitHub Pages。</div><div><br /></div><div>Blazor WASM app 所在的主機當然也可以安裝 .NET,以便能夠使用完整的 .NET API。為了明確區分,如果後端是 ASP.NET Core 伺服器,微軟把它叫做 <b>hosted</b> Blazor WebAssembly app,反之則稱作 <b>standalone</b> Blazor WebAssembly app。</div><blockquote><div>💬 下一篇筆記就會看到,使用 Visual Studio 建立新專案時,如果專案範本選擇「Blazor WebAssembly App」,就會有額外的選項讓你指定是否要建立 hosted app(實際的選項名稱是 ASP.NET Core hosted)。</div></blockquote><div><br /></div><div>接著來看一下,當使用者在網頁上點某個連結來進入一個新頁面時,在 WebAssembly app 裡面發生了哪些事。如下圖:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTc9v05IQuWx7zxdXM2S6bsnzJV2YeteHb-FdUlUsfuyrDDStzVAJ0tX9pZ5y-stbqrEEcr25DhaQGLr_68vViFFakpnKPBv3_jvfiBV50mZs9_A3_uLQdt4-yVvgfAtPjZBqt3oGCySJCzT2h5en9meIuyDUVio8P6HELyo7ZwzyA6LkVqlpc9Mpn/s577/blazor-client-side-navigation.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="385" data-original-width="577" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTc9v05IQuWx7zxdXM2S6bsnzJV2YeteHb-FdUlUsfuyrDDStzVAJ0tX9pZ5y-stbqrEEcr25DhaQGLr_68vViFFakpnKPBv3_jvfiBV50mZs9_A3_uLQdt4-yVvgfAtPjZBqt3oGCySJCzT2h5en9meIuyDUVio8P6HELyo7ZwzyA6LkVqlpc9Mpn/s16000/blazor-client-side-navigation.drawio.png" /></a></div><br /><div>當使用者點擊頁面上的 Counter 連結時,便會觸發一個導覽事件(navigation event),而此事件將由 Blazor 內建的 JavaScript(blazor.webassembly.js)攔截,轉送給 Blazor 執行環境(dotnet.wasm)之後,再由 Blazor 的路由機制接手處理。</div><div><br /></div><div>接下來,Blazor 的路由機制會從路由表中找到符合目前請求的路由元件,即前面範例中的 <span style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Counter</span> 元件,然後建立此元件的實例,並觸發元件的相關事件和方法。</div><div><br /></div><div>一旦 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Counter</span> 元件的工作完成,Blazor 會找出 DOM 裡面需要變更哪些元素,然後把這些變更傳遞給下層的 Blazor JavaScript 來更新 DOM 的內容。然後,使用者便可在瀏覽器中看到 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">Counter</span> 頁面的內容。</div><div><br /></div><div>在 standalone Blazor WASM app 中,上述過程全都發生在前端瀏覽器,並未涉及任何後端主機的處理。</div><div><br /></div><h3 style="text-align: left;">Blazor Server app</h3><div>在 Blazor Server app 中,元件是執行於後端伺服器,而非前端瀏覽器。瀏覽器中觸發的 UI 事件會經由即時的網路連線傳送至伺服器,然後在伺服器端分派至各個元件。接下來,元件把需要更新的網頁內容填入 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">RenderTree</span>(稍早提過),再由 Blazor 計算 UI 差異,並將這些差異回傳至瀏覽器,再由瀏覽器中的 Blazor JavaScript 來更新 DOM。</div><blockquote><div>💬 如果你曾用過早期的 ASP.NET AJAX,你可以把 Blazor Server 裝載模型理解成當時的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #57606a; font-size: 13.6px;">UpdatePanel</span> 控制項。該控制項的運作原理就是依事件的觸發來對網頁進行局部更新,而無須刷新整個頁面。不過,兩者還是有一些差異,例如 UI 的狀態,<span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #57606a; font-size: 13.6px;">UpdatePanel</span> 控制項是透過 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #57606a; font-size: 13.6px;">ViewState</span> 來保存狀態,Blazor Server app 的所有 UI 狀態則是全部保存於伺服器端。</div></blockquote><h4 style="text-align: left;">啟動過程</h4><div>Blazor WebAssembly app 的運作方式比較接近桌上型應用程式,每一個用戶端都會啟動一個 app 的執行個體。Blazor Server app 則是在後端主機由一個執行個體來服務眾多用戶端。</div><div><br /></div><div>下圖為 Blazor Server app 的啟動過程:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinizAIsjNX4CsxfpE2sPz8l-ETR1rJUjSaRuB76eKo3hu8kXuysUpcYIRuDztK2suOzN3AApfOz2W_t-JbV4gZ7J8vNL54D0csx7D-dd1g4KNxZ-GIA_PQRRg6C-5HUzysE2nqnsfi7wbj4N-wyiDnCfOym7Tf0aQywCO6rGbMNyedHYwq_wkXAXbp/s698/blazor-server-app-boot-up.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="337" data-original-width="698" height="308" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinizAIsjNX4CsxfpE2sPz8l-ETR1rJUjSaRuB76eKo3hu8kXuysUpcYIRuDztK2suOzN3AApfOz2W_t-JbV4gZ7J8vNL54D0csx7D-dd1g4KNxZ-GIA_PQRRg6C-5HUzysE2nqnsfi7wbj4N-wyiDnCfOym7Tf0aQywCO6rGbMNyedHYwq_wkXAXbp/w640-h308/blazor-server-app-boot-up.drawio.png" width="640" /></a></div><br /><h3 style="text-align: left;">該選哪一種裝載模型?</h3><div>本節整理 Blazor 兩種裝載模型各有何優缺點,以便協助我們根據實際工作的需求來選出較合適者。</div><div><br /></div><div>Blazor WebAssembly app 的優點:</div><div><ul style="text-align: left;"><li><b>可完全執行於用戶端</b>:當大部分的工作都由前端負責執行,後端主機的工作負荷便可大幅降低,這表示後端主機的硬體成本也較低,而應用程式也能夠有較佳的延展性(scalability)。</li><li><b>可離線作業</b>:用戶端不需要頻繁與後端主機溝通,所以即使網路不夠穩定,甚至網路完全斷線,對使用者也不會造成太大的影響。</li><li><b>以靜態檔案的方式部署</b>:Blazor WebAssembly app 是純粹的用戶端應用程式,故可部署於任何支援靜態網頁的平台,例如 GitHub Pages、Azure Static WebSite Hosting 等等。應用程式本身在進行頁面跳轉時,會將頁面請求重新導向至 app 的根網址(或者說基礎網址;base URI)。</li><li><b>共用程式碼</b>:雖然 Blazor WebAssembly app 並不需要在後端主機上面安裝 .NET,但如果後端主機上面也有 .NET,開發人員將更容易讓前後兩端共用 C# 程式碼(包括相關的套件、框架、和工具)。</li></ul></div><div><br /></div><div>Blazor WebAssembly app 的缺點:</div><div><ul style="text-align: left;"><li><b>載入時間較長</b>:初次執行 app 時需要花較多時間來下載檔案。一旦相關的 .NET 組件都下載至用戶端,下次執行 app 就不再需要重新下載那些檔案,載入速度便會快得多。</li><li><b>受限於瀏覽器</b>:Blazor WebAssembly app 跟 JavaScript 程式一樣都是執行於瀏覽器的沙箱環境,故可存取的資源也有所限制,例如無法存取本機的檔案。</li><li><b>程式的機密性較低</b>:所有程式檔案都下載至用戶端,表示使用者很容易取得應用程式的 DLL 檔案來進行逆向工程或其他破解操作。如果程式部署在後端主機,就比較沒有這類顧慮。如果程式碼包含某些特別機密的資料,最好還是放在後端主機比較保險。</li></ul></div><div><br /></div><div>接著是 Blazor Server app 的優點:</div><div><ul style="text-align: left;"><li><b>載入時間較短</b>:用戶端需要下載的檔案大小比 Blazor WebAssembly app 小很多,所以應用程式的載入速度也更快。</li><li><b>可使用 .NET 完整的 API</b>:後端主機有完整的 .NET 執行環境,應用程式可以發揮 .NET API 的全部功能。</li><li><b>程式的機密性較高</b>:用戶端無法取得放在後端主機的程式碼。</li></ul></div><div><br /></div><div>Blazor Server app 的缺點:</div><div><ul style="text-align: left;"><li><b>主機的負荷較重</b></li><li><b>網路延遲</b></li><li><b>需要穩定的網路連線</b></li></ul></div><div><br /></div><div>簡單來說,如果你需要伺服器的強大運算功能與各種服務(例如資料庫),那就選 Blazor Sever app;如果應用程式的運作方式比較接近桌上型應用程式,而且不太需要網路隨時連線,便可優先考慮 Blazor WebAssembly app。</div><div><br /></div><h3 style="text-align: left;">Hosted Blazor WASM app vs. Blazor Server app</h3><div>一個常見的問題是,hosted Blazor WASM app 跟 Blazer Server app 的後端都有 ASP.NET Core,兩者有何差異?又該如何選擇呢?</div><div><br /></div><div>前面提過,standalone Blazor WASM app 是完全的靜態網頁,只要應用程式相關檔案(包含 .NET 組件)下載至用戶端瀏覽器,後續的使用者操作便全都發生於瀏覽器中。可是,如果程式在某些時候需要呼叫 Web API 呢?若有這種需求,standalone Blazor WASM app 便會碰到許多問題,其中一個立即而明顯的問題是:<a href="https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS" target="_blank">跨來源資源共用</a>(Cross-Origin Resource Sharing),簡稱CORS。另外還有一些限制,大多是基於瀏覽器沙箱本身的安全規範,使得 standalone Blazor WASM app 在呼叫第三方 Web API 或對任意網站發出 HTTP 請求時,會不斷碰到 PlatformNotSupported 例外。這些例外有許多是由 Blazor WASM app 內部的 <a href="https://www.facebook.com/flx/warn/?u=https%3A%2F%2Fgithub.com%2Fdotnet%2Fruntime%2Fblob%2Fdc2d0cc61f58fde5c04b5a29ba476ccd3779ab74%2Fsrc%2Flibraries%2FSystem.Net.Http%2Fsrc%2FSystem%2FNet%2FHttp%2FBrowserHttpHandler%2FBrowserHttpHandler.cs%3Ffbclid%3DIwAR2ToxpEKbXurLVyx0dyiH3nxTvq8pjG8fSyQmBFHIMUjjkyQJM0ZyKy29s&h=AT1_XdiQVfolvhA5_F3WRj-7YQVBEFWeU9TbZU7KwLvQ6MTDgBe6BIY8u9sSbiIUKSwW4a10QZxUN60LFo9THD5yQj6kk9eKCq-MKrmsvMQd1RIkq0hKYmyKV9FiDbHEbngq6DOb" target="_blank">BrowserHttpHandler</a> 所拋出,如下圖,可以看到一部分不支援的屬性,例如 AutomaticDecompression、Proxy 等等。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfgvb4D2q7wpOK-MyDZnIy70a8GAhJwbA8fPsGdJG5NqCgE_gUY0jc8ycm8VENi8DC0urRniY8eJqyVjwoCvgehWuqE7_sjnsa6AVx8DoeYKnet-4m9upwyKJ56VrAHUpu7leOVOfhBJNXfBw5jaDHUXm2BcznJKMeCK-cFOKhL7hOS9S8C5zbxyYi/s1375/browser-http-handler-cs.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="767" data-original-width="1375" height="358" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfgvb4D2q7wpOK-MyDZnIy70a8GAhJwbA8fPsGdJG5NqCgE_gUY0jc8ycm8VENi8DC0urRniY8eJqyVjwoCvgehWuqE7_sjnsa6AVx8DoeYKnet-4m9upwyKJ56VrAHUpu7leOVOfhBJNXfBw5jaDHUXm2BcznJKMeCK-cFOKhL7hOS9S8C5zbxyYi/w640-h358/browser-http-handler-cs.png" width="640" /></a></div><br /><div><br /></div><div><br /></div><div>所以簡單地說,如果應用程式需要呼叫 Web API,基本上就不用考慮 standalone Blazor WASM app。剩下的選擇,就是 hosted Blazor WASM 或者 Blazor Server。</div><div><br /></div><div>Blazor Server 是純粹的前後端分離,亦即 .NET 程式碼都在後端執行,前端則全都是靜態內容。至於 hosted Blazor WASM app,雖然此模型在後端主機上面也有 ASP.NET Core,但是大部分的 .NET 程式碼仍是下載至前端瀏覽器中執行;除了前面提過的 Blazor WASM 裝載模型的優點,hosted Blazor WASM 模型還有一個明顯的好處:我們可以在後端寫自己的 Web API,以便讓前端的 Blazor WASM app 使用。如此一來,便可輕易解決剛才提到的 CORS 以及瀏覽器沙箱的一些安全限制。</div><h3 style="text-align: left;">另一種選擇:Blazor Hybrid</h3><div>關於 Blazor Hybrid 的部分,容我在此偷個懶,僅列出我寫過的筆記以及微軟文章連結:</div><div><ul style="text-align: left;"><li><a href="https://www.huanlintalk.com/2022/07/a-first-look-at-net-blazor-hybrid-app.html" target="_blank">.NET Blazor Hybrid app with MAUI 初體驗</a></li><li><a href="https://learn.microsoft.com/zh-tw/aspnet/core/blazor/hybrid/" target="_blank">微軟文章:ASP.NET Core Blazor Hybrid</a></li></ul></div><h2 style="text-align: left;">重點回顧</h2><div><ul style="text-align: left;"><li>Blazor 應用程式的運行方式,與其說是請求—回應模型,倒不如說是事件驅動模型,更為貼切。</li><li>Blazor 應用程式是以元件來組合 UI。所謂的元件,具體來說就是 .razor 檔案。一個 .razor 檔案頂端如果有寫 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: rgba(175, 184, 193, 0.2); color: #24292f; font-size: 13.6px;">@page</span> 指示詞,就是一個可路由元件(routable component),也就是 page 元件,而我更喜歡這樣叫它:可跳轉頁面。</li><li>Blazor 有兩種裝載模型:Blazor WebAssembly app(簡稱 Blazor WASM)和 Blazor Server app。</li><li>Blazor WebAssembly app 可以建構成純粹的用戶端應用程式,故可部署於任何支援靜態網頁的平台,例如 GitHub Pages、Azure Static WebSite Hosting 等等。這表示後端主機上面不需要安裝 .NET,主機的硬體要求較低,工作負荷也較低。</li><li>Blazor WASM 模型又可分為 standalone WASM 和 hosted WASM;前者是程式碼完全執行於用戶端瀏覽器(將受限於瀏覽器沙箱的安全規範),後者則是後端主機有安裝 ASP.NET Core,故可於後端提供 Web API 供前端 WASM app 調用。還有另一個選擇:Blazord Hybrid。</li></ul><div><a href="https://www.huanlintalk.com/2022/12/net-blazor-your-first-app-with-custom.html" target="_blank">下一篇筆記</a>將會開始動手撰寫第一個 Blazor WebAssembly app,並詳細拆解專案結構,包括:程式進入點、起始頁面(index.html)、根元件(App.razor)、版面配置元件等等。</div></div></div><div><br /></div><div>最後附上我寫這篇筆記時一併整理的術語中英對照表:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXhF9e7qJ0Ux8Rs08kg8_jkUSFIz3vT7UOr1VBbWLQrG03sMGmURZYtB0yG3DJO044mPA-78DIOtX27OwfIJmdqe0MoW7VkMAPou2J2H0Voxnpe0QCKx03W9Fwxe4liQyCqfmwlbQXmHvtZPbAWQJ8TXseMG_edV57lO0_MN7y92G59my7TPDLgNHF/s803/blazor-terms.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="545" data-original-width="803" height="434" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXhF9e7qJ0Ux8Rs08kg8_jkUSFIz3vT7UOr1VBbWLQrG03sMGmURZYtB0yG3DJO044mPA-78DIOtX27OwfIJmdqe0MoW7VkMAPou2J2H0Voxnpe0QCKx03W9Fwxe4liQyCqfmwlbQXmHvtZPbAWQJ8TXseMG_edV57lO0_MN7y92G59my7TPDLgNHF/w640-h434/blazor-terms.png" width="640" /></a></div><br /><div>Keep coding!</div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-8366629872249326912022-11-08T13:30:00.004+08:002022-11-09T19:31:15.499+08:00第一支 .NET MAUI 程式 (for Windows)<p>基於學習的目的,我開始用 MAUI 來寫一些執行於 Windows 平台的小工具。這篇筆記會包含兩個入門的小練習,並解說 MAUI 專案的檔案結構。</p><span><a name='more'></a></span><div><b><br /></b></div><div><b>內容大綱:</b></div><div><ol><li>開發環境</li><li>第一支 MAUI 程式</li><li>了解 MAUI 專案結構</li><li>MainPage</li><li>練習一:增加一個按鈕並撰寫事件處理常式</li><li>練習二:修改視窗大小與位置(限定 Windows 平台)</li><li>練習三:開啟新視窗(跨平台,但通常只用於 Windows)</li><li>練習四:用 Visual Live Tree 觀察視窗內部結構</li><li>結語</li></ol></div><h2>開發環境</h2><p>預設情況下,Windows 會防止使用者執行那些沒有簽章的應用程式。因此,我們必須開啟 Windows 的「開發人員模式」,以便在本機開發和除錯 .NET MAUI 程式。</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8zNxeEHOQ7bgxLE1Llmp4zMbArhoq2pgKAhHD7zSunzm5vYbwnJkaz3dAK8O1sqo1QrgU91GodOahZG6KLN2AI-p73pYnVO5kadQxPcAsdKL1jVbla3OBLnZSgIHmMSWd7cu7lFXEq8cTkZ49iwxDEYrOQ-OQ5q_-ms1joo7qhBRVKZRNEtIqtAkc/s1081/2022-10-26_16-17-59.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="779" data-original-width="1081" height="462" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8zNxeEHOQ7bgxLE1Llmp4zMbArhoq2pgKAhHD7zSunzm5vYbwnJkaz3dAK8O1sqo1QrgU91GodOahZG6KLN2AI-p73pYnVO5kadQxPcAsdKL1jVbla3OBLnZSgIHmMSWd7cu7lFXEq8cTkZ49iwxDEYrOQ-OQ5q_-ms1joo7qhBRVKZRNEtIqtAkc/w640-h462/2022-10-26_16-17-59.png" width="640" /></a></div><br /><p>有關此設定的操作步驟亦可參考微軟文件:<a href="https://learn.microsoft.com/en-us/dotnet/maui/windows/setup" target="_blank">Deploy and debug your .NET MAUI app on Windows</a>。</p><p><br /></p><p>接著便可以開始寫第一支 MAUI 程式,我用的工具是 Visual Studio 2022 預覽版 17.4.0 Preview 5 。應用程式的 Target Framework 是 .NET 7,我的電腦上安裝的 .NET 版本是 7.0.100-rc.2.22477.23。</p><h2>第一支 MAUI 程式</h2><p>從 Visual Studio 建立一個新專案,專案範本選擇「.NET MAUI App」。專案建立完成後,先執行看看應用程式長什麼樣子。詳細的操作步驟可參考微軟文件:<a href="https://learn.microsoft.com/en-us/dotnet/maui/get-started/first-app?tabs=vswin&pivots=devices-windows" target="_blank">Build your first app</a>。下圖是在我的機器上執行的結果:</p><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg3mU94-XVHZsgyJ5APciF2EsVAp8y6T2x-gGT0wJUANmPfV1BHLw9k3eBx9GrfbFwIKnYvKtSwswG2wqvZZAoYWsJg-ctnvXk7-Q_-B6gwCZDd42SSUqVMwvNYmb4Ui0cn9-ri1X9rGZibXavxbvsSnvSVGmYN53VFsHlZ9OspLxDky1J3kT7FyRW/s540/2022-10-26_16-56-35.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="540" data-original-width="476" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg3mU94-XVHZsgyJ5APciF2EsVAp8y6T2x-gGT0wJUANmPfV1BHLw9k3eBx9GrfbFwIKnYvKtSwswG2wqvZZAoYWsJg-ctnvXk7-Q_-B6gwCZDd42SSUqVMwvNYmb4Ui0cn9-ri1X9rGZibXavxbvsSnvSVGmYN53VFsHlZ9OspLxDky1J3kT7FyRW/s16000/2022-10-26_16-56-35.png" /></a></div><br /><blockquote><p>註:畫面中間的 .NET 吉祥物圖案是我改過的。你可以<a href="https://mod-dotnet-bot.net/create-your-bot/" target="_blank">點此連結訂製吉祥物</a>,然後下載製作好的圖片。稍後會說明如何置換此圖片。</p></blockquote><p>到 Solution Explorer 視窗裡面查看專案中有哪些程式檔案,然後在應用程式執行的狀態下,按以下步驟修改程式碼:</p><ol><li>開啟 MainPage.xaml。</li><li>找到 Label 元素,把 Text 屬性值從原本的 "Hello, World!" 改為 "我的第一支 MAUI 程式!"。</li><li>切換至執行中的應用程式,此時應該會看到視窗中已經出現剛才輸入的文字。此步驟可觀察 Visual Studio 的 Hot Reload 功能是否正常運作。</li><li>把自製的 .NET 吉祥物圖檔以滑鼠拖曳至 Solution Explorer 的 Resources\Images 資料夾(也可以用加入檔案的方式),並查看 Properties 視窗以確認該檔案的 Build Action 是「MauiImage」。<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA9Qcc1Mi9K2a5hHr2tYYwiM16gsXoByDy2aNAg4wpcqDMCW9M-cYo_QMN96iruBzKTODaL-8uTWB5Jvo9BVRPyDmYMUVTD5gMqPtRMJ3-_V0ecPvT-Su0f3fNcbnro1XWHh3IrbBBdzwNqGQRMFTKW4vTOmWfKXxR6mqon-xfnAhJ72EOwkeXvzD7/s764/2022-10-28_05-24-15.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="419" data-original-width="764" height="219" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA9Qcc1Mi9K2a5hHr2tYYwiM16gsXoByDy2aNAg4wpcqDMCW9M-cYo_QMN96iruBzKTODaL-8uTWB5Jvo9BVRPyDmYMUVTD5gMqPtRMJ3-_V0ecPvT-Su0f3fNcbnro1XWHh3IrbBBdzwNqGQRMFTKW4vTOmWfKXxR6mqon-xfnAhJ72EOwkeXvzD7/w400-h219/2022-10-28_05-24-15.png" width="400" /></a></div><br /></li><li>回到 MainPage.xaml,找到 Image 元素,把 Source 屬性從原本的 "dotnet_bot.png" 改為自製的吉祥物圖片檔名,然後存檔。如果應用程式仍在執行狀態,而且 Hot Reload 功能運作正常,此時應該會看到應用程式視窗中的吉祥物圖案被換掉了。</li></ol><br /><blockquote><p>註:Hot Reload 是 .NET 的一項功能,它能讓我們在應用程式執行時一邊修改程式,一邊立刻看到修改的結果。</p></blockquote><h2>了解 MAUI 專案結構</h2><p>在 Solution Explorer 中,可以從 .NET MAUI 專案的根目錄底下看到四個檔案,分別是 App.xaml、AppShell.xaml、MainPage.xaml、和 MauiProgram.cs,如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyAkP1uPrSSDNkuwXZHcrhszwbytOFK7VtVL384VKzwyE5RIoIGQsIttVR0tZOmafi7LF6iC4_DksRMD4-pXv7Q9wXztokikHhAmJJ9q3hr61x6Sayay40kwY3ns5RchbSDRXXG9iiz2eFSRbxvmOK5eosH_bn7-bIsrtKCwnHGMDLgvfkSjLRqhcn/s233/2022-10-27_13-53-16.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="233" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyAkP1uPrSSDNkuwXZHcrhszwbytOFK7VtVL384VKzwyE5RIoIGQsIttVR0tZOmafi7LF6iC4_DksRMD4-pXv7Q9wXztokikHhAmJJ9q3hr61x6Sayay40kwY3ns5RchbSDRXXG9iiz2eFSRbxvmOK5eosH_bn7-bIsrtKCwnHGMDLgvfkSjLRqhcn/s1600/2022-10-27_13-53-16.png" width="233" /></a></div><br /><p>接著說明這四個檔案之間的關係。</p><p><br /></p><p>首先要看的是 MauiProgram.cs,裡面有應用程式的進入點,如下圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlq06raetqX-R5-paoYZdlxceFJCWW7CB9wOEIiTLWIGKY_fiYKOz5qSWVld4WqAYLGzdV-4QDp9P57wUdlrqtQ75h6SRAIbUuWF7qnaXqOyVHKbN8AM5uCuObMiPJvOpDg4tRMc-BPxwTatqqDeIkcFJ90-c6EqyzgfviUwtG9OS-xnRApQ3rqBKx/s822/2022-10-27_13-59-54.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="427" data-original-width="822" height="332" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlq06raetqX-R5-paoYZdlxceFJCWW7CB9wOEIiTLWIGKY_fiYKOz5qSWVld4WqAYLGzdV-4QDp9P57wUdlrqtQ75h6SRAIbUuWF7qnaXqOyVHKbN8AM5uCuObMiPJvOpDg4tRMc-BPxwTatqqDeIkcFJ90-c6EqyzgfviUwtG9OS-xnRApQ3rqBKx/w640-h332/2022-10-27_13-59-54.png" width="640" /></a></div><br /><p>上圖的第 11 行,也就是 UseMauiApp<App>(),其作用是註冊此應用程式,而應用程式的類別名稱是 "App"。這個 App 類別就是專案根目錄下的 App.xaml 以及與之關聯的 C# 檔案 App.xaml.cs。下圖即為 App.xaml.cs 的內容:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUSE7r9IASZ7H8kdJZpEdEmtMNvTqYjRQcL1JidzAlh7FzlgEds8Nc1TZYOQJ7yP9A1Lcm65FRUFRmjqJxsp61iS8gGQObp3fOL5vDifC7f8teo0pE3t8aNMABv6K57sVF1YOWTHgLVVBaAqWjd3xOyBYGpHKztOmvxQ_SDNBzkzmLdlWcWKjsrD_Q/s478/2022-10-27_14-05-40.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="209" data-original-width="478" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUSE7r9IASZ7H8kdJZpEdEmtMNvTqYjRQcL1JidzAlh7FzlgEds8Nc1TZYOQJ7yP9A1Lcm65FRUFRmjqJxsp61iS8gGQObp3fOL5vDifC7f8teo0pE3t8aNMABv6K57sVF1YOWTHgLVVBaAqWjd3xOyBYGpHKztOmvxQ_SDNBzkzmLdlWcWKjsrD_Q/s16000/2022-10-27_14-05-40.png" /></a></div><br /><p>第 9 行指定了此應用程式的主頁是 AppShell,此類別即定義於專案根目錄下的 AppShell.xaml 以及與之關聯的 AppShell.xaml.cs。下圖為 AppShell.xaml 的內容:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuTwPuLD6D9ZP-Z5psCVgOSgX0e6JhtC-_i5mape4eXsmu5e4-BlAe1MgXJJqfKZSPvVbEXw0j0-hoLknaGh4GIZeye1bBUsw_pJOwvG9GT4VfIt56-R0-VwO_pmgfIAoEsnp5eq3BdcIfWnLPlgLrBedR6xeAzcEhmGvjmfwJVfsXFVlwLV-Om3FW/s623/2022-10-27_14-12-35.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="277" data-original-width="623" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuTwPuLD6D9ZP-Z5psCVgOSgX0e6JhtC-_i5mape4eXsmu5e4-BlAe1MgXJJqfKZSPvVbEXw0j0-hoLknaGh4GIZeye1bBUsw_pJOwvG9GT4VfIt56-R0-VwO_pmgfIAoEsnp5eq3BdcIfWnLPlgLrBedR6xeAzcEhmGvjmfwJVfsXFVlwLV-Om3FW/s16000/2022-10-27_14-12-35.png" /></a></div><br /><p>顧名思義,AppShell 就是應用程式的「外殼」,它定義了應用程式的起始畫面。如上圖所示,ShellContent 元素定義了主頁是由 MainPage 類別負責,也就是專案根目錄下的 MainPage.xaml 和 MainPage.xaml.cs。</p><p><br /></p><p>到目前為止,我們從應用程式進入點 MauiProgram.cs 開始順藤摸瓜,依序看了 App.xaml、AppShell.xaml,以即 MainPage.xaml。這四個檔案之間的順序和關聯,應該大致清楚了。</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhD3Pe-TfijmdMYfEg8UXsIOMYIMV-DBcs9hIXuiYM3PX9vn-3Gqbr2Q-5io2v8IKhHsXbA56iFq5ADyygVL-QC1LmPnF3eg1evAK_PpMjYCHyN_NpRFWOB5b5t05qxnEUYgO5_PVF56ySDdWfxyzk0SkhpKqT6G7MlyyDbo0U2SvZB1EhI0XiuDy1l/s308/2022-10-27_16-22-30.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="308" data-original-width="303" height="308" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhD3Pe-TfijmdMYfEg8UXsIOMYIMV-DBcs9hIXuiYM3PX9vn-3Gqbr2Q-5io2v8IKhHsXbA56iFq5ADyygVL-QC1LmPnF3eg1evAK_PpMjYCHyN_NpRFWOB5b5t05qxnEUYgO5_PVF56ySDdWfxyzk0SkhpKqT6G7MlyyDbo0U2SvZB1EhI0XiuDy1l/s1600/2022-10-27_16-22-30.png" width="303" /></a></div><br /><p>接下來,可以花更多時間看一下 MainPage.xaml 和它的 C# 檔案 MainPage.xaml.cs。</p><h2>MainPage</h2><p>基本上,.xaml 檔案是用來放 UI 元素,並設定元素的屬性值,而 C# 檔案則用來撰寫 UI 的互動邏輯。比如說,按鈕按下的事件處理常式,我們可以在 MainPage.xaml 裡面看到 CounterBtn 這個按鈕的元素的 Clicked 屬性被設定成 "OnCounterClicked":</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjl5xt1uOcS-iJx2zHfyxFBx5R6N6PIj27qOt7UxvQhRXutTi632kJjhHuXk-h37pYVVpe-98wvYVl1r_bXCZICgvIo16VSBRZYQwJX0cSxhrbApO5Z6EEXXkGu7DVBcBUJgG0RbYLoAGLFB6Ky4TxLhhaSg647LALpc6iPNw8aKmPoomGSgZml3G8u/s812/2022-10-27_16-48-50.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="372" data-original-width="812" height="293" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjl5xt1uOcS-iJx2zHfyxFBx5R6N6PIj27qOt7UxvQhRXutTi632kJjhHuXk-h37pYVVpe-98wvYVl1r_bXCZICgvIo16VSBRZYQwJX0cSxhrbApO5Z6EEXXkGu7DVBcBUJgG0RbYLoAGLFB6Ky4TxLhhaSg647LALpc6iPNw8aKmPoomGSgZml3G8u/w640-h293/2022-10-27_16-48-50.png" width="640" /></a></div><br /><p>OnCounterClicked 是一個函式,寫在 MainPage.xaml.cs 裡面,如下圖。</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvdYM2EpSq3T-vv6WdyaumkAGgZYIA8Ndc1SrYKc4v7yaJ8YIxKTDsBv8faQJZ-lk7_iC8LzPHK9BNigC357TRXYtn6HP9IW6Sd3d897sUNGqMa6e7ggX74VQNYxZvCOnrvpA7gTdY_wfRUp--zod_QEyYsHjQK5h0KVa4Sg2MFCwlQZgqJSa_Z65p/s705/2022-10-27_16-29-44.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="458" data-original-width="705" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvdYM2EpSq3T-vv6WdyaumkAGgZYIA8Ndc1SrYKc4v7yaJ8YIxKTDsBv8faQJZ-lk7_iC8LzPHK9BNigC357TRXYtn6HP9IW6Sd3d897sUNGqMa6e7ggX74VQNYxZvCOnrvpA7gTdY_wfRUp--zod_QEyYsHjQK5h0KVa4Sg2MFCwlQZgqJSa_Z65p/w640-h416/2022-10-27_16-29-44.png" width="640" /></a></div><br /><p>其中比較特別的是第 21 行,呼叫 SemanticScreenReader.Announce() 的作用是讓螢幕報讀軟體讀出傳入該函式的文字,以便視障者透過聽覺來了解目前的狀況。如果把這行拿掉,那麼當按鈕按下時,即使目前的 Windows 環境有啟動語音報讀軟體,也不會報讀 CounterBtn 按鈕上面的文字,而必須把輸入焦點移動至那個按鈕,報讀軟體才會讀出按鈕文字。</p><br /><p>👉 參考微軟文件:<a href="https://learn.microsoft.com/zh-tw/dotnet/maui/fundamentals/accessibility" target="_blank">使用語意屬性建置無障礙應用程式</a></p><h2>練習一:增加一個按鈕並撰寫事件處理常式</h2><p>接著來做一個小練習:在 MainPage 上面增加一個按鈕,並且在按鈕按下時彈出一個對話窗來顯示當前的日期時間。操作步驟可參考以下短片,就不寫成文字了(亦可<a href="https://youtu.be/kgGa8n_Gprg" target="_blank">至 Youtube 觀看</a>)。</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="526" src="https://www.youtube.com/embed/kgGa8n_Gprg" width="633" youtube-src-id="kgGa8n_Gprg"></iframe></div><br /><h2>練習二:修改視窗大小與位置</h2><p>我在寫這個小程式的時候,是以 Windows 平台為目標,並沒有打算執行於 Android 或 iOS 行動裝置,所以有一些想要嘗試的點子是來自以往開發 Windows Forms 程式的經驗。比如說,在 Windows Forms 應用程式中,如果想要在主視窗載入時設定視窗大小,只要在 Form 的 Load 事件處理常式中設定 Width 和 Height 屬性就行了。然而,這件小事在 MAUI 應用程式當中並沒有我想像的那麼容易。</p><p><br /></p><p>起初,我是在 stackoverflow 找到解法,帖子標題是:<a href="https://stackoverflow.com/questions/72399551/maui-net-set-window-size" target="_blank">MAUI .NET Set Window Size</a>。亦可參考以下截圖:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiMbjzqG7s-k_LwQuqOP0pLOD2k6mp1dxeKIXiF3wwBIUI1pYO3gib7kEcvLfCwKqyGkLjzwu7sTKrkqW7f6rkuhD2rK7zI_eCKmrV51YloanwMQZFM4GJaDxgnMSiZ9Q_8OpryBBRXrOJkC3T81Q9b_aqx_CCakTtCjwhy0x7dPQFhzrnaJmkvbOb/s1101/2022-10-28_04-30-39.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="532" data-original-width="1101" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiMbjzqG7s-k_LwQuqOP0pLOD2k6mp1dxeKIXiF3wwBIUI1pYO3gib7kEcvLfCwKqyGkLjzwu7sTKrkqW7f6rkuhD2rK7zI_eCKmrV51YloanwMQZFM4GJaDxgnMSiZ9Q_8OpryBBRXrOJkC3T81Q9b_aqx_CCakTtCjwhy0x7dPQFhzrnaJmkvbOb/w640-h310/2022-10-28_04-30-39.png" width="640" /></a></div><br /><br />此解法需要 12 行程式碼,還得動用 Win32 API,透過視窗代碼(handle)來達成目的。我想應該沒有人喜歡這種寫法。<br /><br />之後,我上網爬了一下,發現原來 GitHub 上面的 MAUI 專案已經有 ticket:<a href="https://github.com/dotnet/maui/pull/4942" target="_blank">Enable Window Sizing</a>,相關功能已經實作完成且合併至主分支。我參考其中的說明,試出以下程式碼可以達到我想要的效果,也就是在<b>主視窗</b>開啟的時候自動設定視窗大小,並顯示於螢幕的正中央。
<br /><br />
<script src="https://gist.github.com/huanlin/30f704e641987a39603cde8f8cf8e3fb.js"></script>
<br />雖然程式碼看似比先前那個使用 Win32 API 的寫法還要長,但那是因為這次還加入了「把視窗移動至螢幕中央」的處理。我選擇在視窗獲得輸入焦點的時候(<span class="pl-smi" face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: white; box-sizing: border-box; color: #24292f; font-size: 13px; font-weight: 700; white-space: pre;">Activated</span><span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: white; color: #24292f; font-size: 13px; font-weight: 700; white-space: pre;"> </span>事件)來改變視窗大小和位置。視窗物件還有哪些事件,也可順便了解一下:<a href="https://learn.microsoft.com/zh-tw/dotnet/maui/fundamentals/app-lifecycle" target="_blank">.NET MAUI 應用程式週期</a>。<br /><div>要特別提出來說明的是第 30 行程式碼,也就是呼叫視窗物件的 <span class="pl-smi" face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: white; box-sizing: border-box; color: #24292f; font-size: 13px; font-weight: 700; white-space: pre;">Dispatcher</span><span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: white; color: #24292f; font-size: 13px; font-weight: 700; white-space: pre;">.</span><span class="pl-en" color="var(--color-prettylights-syntax-entity)" face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: white; box-sizing: border-box; font-size: 13px; font-weight: 700; white-space: pre;">DispatchAsync</span><span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: white; color: #24292f; font-size: 13px; font-weight: 700; white-space: pre;">() </span>方法。如果刪除這行程式碼,那麼第 33 行的 <span face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: white; color: #24292f; font-size: 13px; font-weight: 700; white-space: pre;">MainDisplayInfo </span>將在某些時刻無法取得主螢幕資訊:螢幕解析度的 <span class="pl-en" color="var(--color-prettylights-syntax-entity)" face="ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace" style="background-color: white; box-sizing: border-box; font-size: 13px; font-weight: 700; white-space: pre;">Width</span> 和 <span style="background-color: white; font-size: 13px; font-weight: 700; white-space: pre;">Height</span> 都會是 0,這將導致後續程式碼無法將視窗位置移至螢幕中央。</div>
<br /><div>以上程式碼僅適用於 Windows 平台。如果是 Mac,解法較不直觀,需要設定 Window 物件的 <span style="background-color: white; font-size: 13px; font-weight: 700; white-space: pre;">MinimumWidth</span>、<span style="background-color: white; font-size: 13px; font-weight: 700; white-space: pre;">MaximumWidth</span>、<span style="background-color: white; font-size: 13px; font-weight: 700; white-space: pre;">MinimumHeight</span>、<span style="background-color: white; font-size: 13px; font-weight: 700; white-space: pre;">MaximumHeight</span> 這四個屬性,詳情參見<a href="https://github.com/dotnet/maui/pull/4942" target="_blank">剛才提及的那個 ticket</a>。</div><br /><div><h2>練習三:開啟新視窗</h2></div><div>上一個練習的目的是讓應用程式的「主視窗」開啟時能夠設定視窗的大小和位置,其程式寫法只對應用程式的主視窗有作用。如果主視窗載入之後需要開啟另一個新視窗,那個新視窗的大小和位置就很容易設定了。以下便是開啟新視窗的練習步驟。</div><h3 style="text-align: left;">步驟 1:加入一個新頁</h3><div>在剛才練習的專案中加入一個新的 Content Page:在 Solution Explorer 的專案名稱節點上點滑鼠右鍵,選擇 Add > New Item,然後選擇 .NET MAUI ContentPage (XAML),檔案名稱採用預設的 NewPage1.xaml。如下圖:</div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicGh-nN8LMLlfiC_prT48UOfV-1RiETQqvv-Vsbhv43J2I9lMgiN41l8rwUlaH8M9RKAGCmk_IfJ4KL3B_OKT-YFwtyZPz6HJOUzsc1VN7AIACjrEg6rXQNa2RmMSlznaGgs7AB_MEDCG7m2qv30oIvAA6mhDFOvx41G4IuqiuQf0hJB_ihuk3bLCK/s913/2022-11-08_11-19-37.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="459" data-original-width="913" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicGh-nN8LMLlfiC_prT48UOfV-1RiETQqvv-Vsbhv43J2I9lMgiN41l8rwUlaH8M9RKAGCmk_IfJ4KL3B_OKT-YFwtyZPz6HJOUzsc1VN7AIACjrEg6rXQNa2RmMSlznaGgs7AB_MEDCG7m2qv30oIvAA6mhDFOvx41G4IuqiuQf0hJB_ihuk3bLCK/w640-h322/2022-11-08_11-19-37.png" width="640" /></a></div><br /><div>此新頁面不用加入任何控制項,我們只是要拿它來練習開啟新視窗而已。</div>
<h3 style="text-align: left;">步驟 2:按鈕點擊時開啟新視窗</h3><div>修改 MainPage.xaml.cs 中的 Button1_Clicked 事件處理常式,</div>
<br />
<script src="https://gist.github.com/huanlin/dc8db80f497b3761c0b30ec0406fd421.js"></script>
<br />執行看看,每當滑鼠點一次 Button1,應該就會開啟一個新視窗。連續點三次 Button1 就會開啟三個新視窗。<div><br /></div><div>從剛才的程式碼可知,開啟新視窗的方法是透過 Application 物件的 Current 屬性來取得目前執行中的應用程式,然後呼叫應用程式物件的 <a href="https://learn.microsoft.com/zh-tw/dotnet/api/microsoft.maui.controls.application.openwindow" target="_blank">OpenWindow 方法</a>來開啟新視窗。視窗物件是由 new Window() 來建立,並且在創建視窗物件時傳入 Page 物件。</div><div><br /></div><div>👉 如果要關閉視窗,則是呼叫應用程式物件的 <a href="https://learn.microsoft.com/zh-tw/dotnet/api/microsoft.maui.controls.application.closewindow?view=net-maui-6.0" target="_blank">CloseWindow 方法</a>。</div><div><br /></div><div>稍後的「練習四」有個影片,可以看到應用程式執行起來的樣子。值得一提的是,這種「多視窗」的設計雖然也可以運行於行動裝置,但是不同平台會有不同的呈現方式。</div><h2 style="text-align: left;">練習四:用 Visual Live Tree 觀察視窗內部結構</h2><div><a href="https://youtu.be/WtzIR6V2jfc" target="_blank">直接看影片</a>比較快:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="357" src="https://www.youtube.com/embed/WtzIR6V2jfc" width="517" youtube-src-id="WtzIR6V2jfc"></iframe></div><div><br />
<h2>結語</h2><p>雖然我正在寫的這個小工具只會執行於 Windows 平台,使用 MAUI 感覺有點 overkill 了,但是基於學習的目的,我還是會繼續添加一些功能,看看最終能走到哪裡、以及會碰到哪些問題。</p><p><br /></p><p>先這樣吧。Keep coding!</p><h2>延伸閱讀</h2><ul><li><a href="https://www.huanlintalk.com/2022/07/a-brief-history-towards-net-maui.html" target="_blank">.NET MAUI 簡介:從 .NET 歷史說起</a></li><li><a href="https://youtu.be/rumfIg9qJ_Y" target="_blank">.NET MAUI Tutorial for Beginners - Build iOS, Android, macOS, & Windows Apps with C# & Visual Studio</a> by James Montemagno(長達一小時的教學影片)</li><li>微軟文件:<a href="https://learn.microsoft.com/zh-tw/dotnet/maui/fundamentals/accessibility" target="_blank">使用語意屬性建置無障礙應用程式</a></li><li>微軟文件:<a href="https://learn.microsoft.com/zh-tw/dotnet/maui/fundamentals/app-lifecycle" target="_blank">.NET MAUI 應用程式週期<br /></a></li></ul>
<br /></div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-79654260270976574932022-10-11T14:16:00.011+08:002022-10-12T12:00:37.654+08:00在多執行緒程式中使用 Random 類別的注意事項<p><span face="-apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"" style="background-color: white; box-sizing: border-box; color: #333333; font-size: 16px; letter-spacing: 0.35px;">我看到 <a href="https://www.youtube.com/watch?v=WRB4OHpSXHs" target="_blank">Nick Chapsas 近日發布的一個 Youtube 影片</a>,裡面展示了 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; margin: 0px; padding: 0.2em 0px;">Random</code><span face="-apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"" style="background-color: white; box-sizing: border-box; color: #333333; font-size: 16px; letter-spacing: 0.35px;"> 類別在多執行緒應用程式中可能產生的問題,覺得有點意思,便照著他的範例做了一點測試和筆記。</span></p><p><span></span></p><a name='more'></a><span face="-apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"" style="background-color: white; box-sizing: border-box; color: #333333; font-size: 16px; letter-spacing: 0.35px;"><br /></span><p></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">匆忙的人可以先看以下重點:</span></p><ul style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 16px; margin-top: 0px; padding-left: 2em;"><li style="box-sizing: border-box;"><span style="box-sizing: border-box;">在多執行緒環境中使用 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 類別來產生隨機數字可能會出現非預期的(不正確的)結果。非預期的結果指的是:產生的隨機數字會有許多是 0(那就不隨機啦)。.NET 5 (以及更早期的 .NET Framework)的 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 類別皆有此缺陷。</span></li><li style="box-sizing: border-box; padding-top: 0.25em;"><span style="box-sizing: border-box;">.NET 6 的 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 類別已經有針對執行緒安全的問題做出改進,但直到 .NET 7 仍有一點小坑(主要還是為了跟舊版 API 相容,詳見內文)。</span></li><li style="box-sizing: border-box; padding-top: 0.25em;"><span style="box-sizing: border-box;">在 .NET 6 和 .NET 7,使用 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 類別的預設建構子便可放心執行於多執行緒的應用程式;但若使用另一個帶參數的建構子,則必須注意執行緒安全的問題。</span></li></ul><h2 data-id="範例程式" id="範例程式" style="background-color: white; border-bottom: 1px solid rgb(238, 238, 238); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a class="anchor hidden-xs" href="file:///C:/Downloads/%E7%84%A1%E6%A8%99%E9%A1%8C%20(1).html#%E7%AF%84%E4%BE%8B%E7%A8%8B%E5%BC%8F" style="background-color: transparent; box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="範例程式"><span class="octicon octicon-link" style="-webkit-font-smoothing: antialiased; box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><span style="box-sizing: border-box;">範例程式</span></h2><p><span face="-apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"" style="background-color: white; box-sizing: border-box; color: #333333; font-size: 16px; letter-spacing: 0.35px;"></span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">使用以下範例程式來分別觀察執行於 .NET 5、6、7 的結果有何差異:</span></p>
<br />
<script src="https://gist.github.com/huanlin/d526c23ab01139c5a66cc56013cc86c5.js"></script>
<br />
<p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="background-color: transparent; letter-spacing: 0.35px;">說明:</span></p><p></p><ul style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 16px; margin-top: 0px; padding-left: 2em;"><li style="box-sizing: border-box;"><span style="box-sizing: border-box;">第 9 行:在進入平行處理的迴圈之前,建立一個共用的 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 物件。注意這裡使用的是預設建構子,而不是使用其他版本(帶參數)的建構子。</span></li><li style="box-sizing: border-box; padding-top: 0.25em;"><span style="box-sizing: border-box;">第 10 行:使用 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Parallel.For</code><span style="box-sizing: border-box;"> 來建立 16 個平行處理的工作。</span></li><li style="box-sizing: border-box; padding-top: 0.25em;"><span style="box-sizing: border-box;">第 12~16 行:使用先前建立的 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 物件來產生 10,000 個隨機數字,並將數字保存於整數陣列 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">numbers</code><span style="box-sizing: border-box;"> 中。</span></li><li style="box-sizing: border-box; padding-top: 0.25em;"><span style="box-sizing: border-box;">第 18~19 行:檢查 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">numbers</code><span style="box-sizing: border-box;"> 陣列中有幾個 0。正常情況下,隨機函數不應該產生一個以上的 0。這裡將 0 的個數顯示於 Console,以便觀察執行結果是否正常。</span></li></ul><h2 data-id="NET-5(以及更早的版本)" id="NET-5(以及更早的版本)" style="background-color: white; border-bottom: 1px solid rgb(238, 238, 238); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a class="anchor hidden-xs" href="file:///C:/Downloads/%E7%84%A1%E6%A8%99%E9%A1%8C%20(1).html#NET-5%EF%BC%88%E4%BB%A5%E5%8F%8A%E6%9B%B4%E6%97%A9%E7%9A%84%E7%89%88%E6%9C%AC%EF%BC%89" style="background-color: transparent; box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="NET-5(以及更早的版本)"><span class="octicon octicon-link" style="-webkit-font-smoothing: antialiased; box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><span style="box-sizing: border-box;">.NET 5(以及更早的版本)</span></h2><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">我已經把範例程式放到 .NET Fiddle 網站上,你可以直接點以下連結來查看此範例程式執行於 .NET 4.7.2 的結果。</span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><a href="https://dotnetfiddle.net/iUqc7Z" rel="noopener" style="background-color: transparent; box-sizing: border-box; color: #337ab7; text-decoration-line: none;" target="_blank">https://dotnetfiddle.net/iUqc7Z</a></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">執行結果如下圖:</span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;"><br /></span></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgT6YPKDVFk5cKH7HCr71Tc2umPaQ9kTCYJaejfgHwt_G232u8Ek7zqP2GVYKfix5VSqAWxSm8agKV3ViHNW1AtCpxwGgnp53OVuI-Jc8jLB4eIM0JrxQ72I4jWrNPIHPOiUM2kMRksOyN6k5lDLTWXJw_C0OJ8wUlTBiJNs2V7DYQB1C5QoJaD1mt9/s651/2022-10-11_10-53-27.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="570" data-original-width="651" height="560" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgT6YPKDVFk5cKH7HCr71Tc2umPaQ9kTCYJaejfgHwt_G232u8Ek7zqP2GVYKfix5VSqAWxSm8agKV3ViHNW1AtCpxwGgnp53OVuI-Jc8jLB4eIM0JrxQ72I4jWrNPIHPOiUM2kMRksOyN6k5lDLTWXJw_C0OJ8wUlTBiJNs2V7DYQB1C5QoJaD1mt9/w640-h560/2022-10-11_10-53-27.png" width="640" /></a></div><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;"><span style="color: black; font-family: "Times New Roman"; font-size: medium; letter-spacing: 0.35px;"></span></span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="letter-spacing: 0.35px;"><br /></span></p><h2 data-id="NET-6-與-NET-7" id="NET-6-與-NET-7" style="background-color: white; border-bottom: 1px solid rgb(238, 238, 238); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a class="anchor hidden-xs" href="file:///C:/Downloads/%E7%84%A1%E6%A8%99%E9%A1%8C%20(1).html#NET-6-%E8%88%87-NET-7" style="background-color: transparent; box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="NET-6-與-NET-7"><span class="octicon octicon-link" style="-webkit-font-smoothing: antialiased; box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><span style="box-sizing: border-box;">.NET 6 與 .NET 7</span></h2><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">如果將前面範例程式的 Target Framework 改成 .NET 6 或 .NET 7,則不會有剛才的問題。執行結果如下圖:</span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;"><br /></span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_fLtrDeMuPbg5jRl7jCB_y994JNfa7trdMWu2JdodNmLbkQk2MoVLpG7rIiVocSOZ7DQSRhEeE7D7SFkjn4RWGN1xydkD1FORtSCI72Kwk0dGVwgw2bjBoLGtN60QM4WYEiS3yVcjtTfSg_cP03EZG-V9Cgi2l95QFrU-jC1F4TnBNy5myz_Q4ldr/s648/2022-10-11_10-57-51.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="526" data-original-width="648" height="520" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_fLtrDeMuPbg5jRl7jCB_y994JNfa7trdMWu2JdodNmLbkQk2MoVLpG7rIiVocSOZ7DQSRhEeE7D7SFkjn4RWGN1xydkD1FORtSCI72Kwk0dGVwgw2bjBoLGtN60QM4WYEiS3yVcjtTfSg_cP03EZG-V9Cgi2l95QFrU-jC1F4TnBNy5myz_Q4ldr/w640-h520/2022-10-11_10-57-51.png" width="640" /></a></div><br /><span style="box-sizing: border-box;"><br /></span><p></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">同樣的,你也可以點以下連結來查看 .NET Fiddle 的執行結果:</span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><a href="https://dotnetfiddle.net/QfPqtV" rel="noopener" style="background-color: transparent; box-sizing: border-box; color: #337ab7; text-decoration-line: none;" target="_blank">https://dotnetfiddle.net/QfPqtV</a></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">接著在上面的 .NET Fiddle 頁面中修改程式碼,原本建立 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 物件時使用預設建構子,現在改用另一個版本,也就是需要傳入 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">seed</code><span style="box-sizing: border-box;"> 參數的建構子,例如:</span></p><pre style="background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="cs hljs" style="background: initial; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="box-sizing: border-box; color: #a71d5d;">var</span> rng = <span class="hljs-keyword" style="box-sizing: border-box; color: #a71d5d;">new</span> Random(<span class="hljs-number" style="box-sizing: border-box; color: #0086b3;">123</span>);
</code></pre><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">然後執行看看,結果隨機函數又產生一堆 0 了(跟剛才執行於 .NET 5 平台的結果類似)。</span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">🤔<b>為什麼在 .NET 6 和 .NET 7 平台上,使用 </b></span><b><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 的預設建構子就可以正常執行於多執行緒的情境,改用 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random(int seed)</code><span style="box-sizing: border-box;"> 就會產生一堆 0 呢?</span></b></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">把 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 類別的原始碼找出來看,可以發現兩個版本的建構子使用了不同的實作:</span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjigAfuOg7GaESne6VE7oTQQZUQygH76WrowPfZ_J0w2n27PVTtk08r-Tjgh_9psKw4BQdsXI3b2xmUQgLhppYSSZG7oVWFyA-BsUuU-EP-KUMxXhfZaODRS644M1eYGXKvfpgVB-BdHJv2UrKkSghUAAU1mQC9W3qx6gjYXl5wUaegk7oSlwcDWz-P/s1223/2022-10-11_11-20-52.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="428" data-original-width="1223" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjigAfuOg7GaESne6VE7oTQQZUQygH76WrowPfZ_J0w2n27PVTtk08r-Tjgh_9psKw4BQdsXI3b2xmUQgLhppYSSZG7oVWFyA-BsUuU-EP-KUMxXhfZaODRS644M1eYGXKvfpgVB-BdHJv2UrKkSghUAAU1mQC9W3qx6gjYXl5wUaegk7oSlwcDWz-P/w640-h224/2022-10-11_11-20-52.png" width="640" /></a></div><br /><span style="box-sizing: border-box;"><br /></span><p></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">若使用 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 類別的預設建構子,其內部會透過 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">XoshiroImpl</code><span style="box-sizing: border-box;"> 來負責產生隨機數字;這個實作是具備執行緒安全的。</span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">不過,我們可以從原始碼發現一個例外情形:如果使用繼承自 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 的子類別來建立物件,那麼即便是預設建構子,也一樣會使用相容於 .NET 5 時代的舊版實作 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Net5CompatSeedImpl</code><span style="box-sizing: border-box;">(亦即多執行緒環境下產生的隨機數字會有一堆都是 0)。</span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">如果使用 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random(int seed)</code><span style="box-sizing: border-box;"> 來建立物件,則一律使用相容於舊版的實作。這就是為什麼剛才把範例程式改成使用帶參數版本的建構子之後,執行結果會跑出一堆 0 的原因。</span></p><h2 data-id="其他解法" id="其他解法" style="background-color: white; border-bottom: 1px solid rgb(238, 238, 238); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a class="anchor hidden-xs" href="file:///C:/Downloads/%E7%84%A1%E6%A8%99%E9%A1%8C%20(1).html#%E5%85%B6%E4%BB%96%E8%A7%A3%E6%B3%95" style="background-color: transparent; box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="其他解法"><span class="octicon octicon-link" style="-webkit-font-smoothing: antialiased; box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><span style="box-sizing: border-box;">其他解法</span></h2><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">在 .NET 6 和 .NET 7,除了使用 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 的預設建構子,還有一個寫法是把建立 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 物件的工作移至 Parallel.For() 的委派方法中:</span></p><pre style="background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="cs hljs" style="background: initial; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-comment" style="box-sizing: border-box; color: #969896;">// 原本寫在這裡的 var rng = new Random(); </span>
Parallel.For(<span class="hljs-number" style="box-sizing: border-box; color: #0086b3;">0</span>, <span class="hljs-number" style="box-sizing: border-box; color: #0086b3;">16</span>, _ =>
{
<span class="hljs-comment" style="box-sizing: border-box; color: #969896;">// 改移到這裡:</span>
<span class="hljs-keyword" style="box-sizing: border-box; color: #a71d5d;">var</span> rng = <span class="hljs-keyword" style="box-sizing: border-box; color: #a71d5d;">new</span> Random();
<span class="hljs-keyword" style="box-sizing: border-box; color: #a71d5d;">var</span> numbers = <span class="hljs-keyword" style="box-sizing: border-box; color: #a71d5d;">new</span> <span class="hljs-built_in" style="box-sizing: border-box; color: #005cc5;">int</span>[<span class="hljs-number" style="box-sizing: border-box; color: #0086b3;">10000</span>];
...
}
</code></pre><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">原先範例的寫法只建立一個 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 物件給多個執行緒共用,修改之後則變成每一個工作執行緒都有自己專用的 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 物件。此解法雖然能夠解決多執行緒情境下產生一堆 0 的問題,卻會增加一些記憶體用量。<a href="https://learn.microsoft.com/en-us/dotnet/api/system.random?view=net-6.0#ThreadSafety" target="_blank">微軟文件</a>也有提到這點:</span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNwteUQyULyODM9RHYhPIrEighpW9fuCDJ0G3oLgTkHnrgU6EmQNWkqlNvqM1vQFvrdfAQk5kTJdczxl_CAKFs4Eui1nutg9XPYSwgqw36R1Kz052t3WQNonNVe5hL7-grhbaxcWPAgekZL7GwmAykITe-R3NyEXuYcTPkfmZcoXN_QqIYTSOxbMY5/s945/2022-10-11_11-51-36.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="291" data-original-width="945" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNwteUQyULyODM9RHYhPIrEighpW9fuCDJ0G3oLgTkHnrgU6EmQNWkqlNvqM1vQFvrdfAQk5kTJdczxl_CAKFs4Eui1nutg9XPYSwgqw36R1Kz052t3WQNonNVe5hL7-grhbaxcWPAgekZL7GwmAykITe-R3NyEXuYcTPkfmZcoXN_QqIYTSOxbMY5/w640-h198/2022-10-11_11-51-36.png" width="640" /></a></div><span style="letter-spacing: 0.35px;"><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="letter-spacing: 0.35px;"><br /></span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">另一個簡單解法是 .NET 6 替 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit !important; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 類別新增的 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit !important; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Shared</code><span style="box-sizing: border-box;"> 靜態屬性:</span></p><pre style="background-color: #f7f7f7; border-radius: 3px; border: inherit !important; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="cs hljs" style="background: initial; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit !important; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">Parallel.For(<span class="hljs-number" style="box-sizing: border-box; color: #0086b3;">0</span>, <span class="hljs-number" style="box-sizing: border-box; color: #0086b3;">16</span>, _ =>
{
<span class="hljs-keyword" style="box-sizing: border-box; color: #a71d5d;">var</span> numbers = <span class="hljs-keyword" style="box-sizing: border-box; color: #a71d5d;">new</span> <span class="hljs-built_in" style="box-sizing: border-box; color: #005cc5;">int</span>[<span class="hljs-number" style="box-sizing: border-box; color: #0086b3;">10000</span>];
<span class="hljs-keyword" style="box-sizing: border-box; color: #a71d5d;">for</span> (<span class="hljs-keyword" style="box-sizing: border-box; color: #a71d5d;">var</span> i = <span class="hljs-number" style="box-sizing: border-box; color: #0086b3;">0</span>; i < numbers.Length; i++)
{
numbers[i] = Random.Shared.Next();
}
...
}
</code></pre><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="box-sizing: border-box;">如果你的程式是執行於 .NET 5 或更舊的 .NET Framework,或者你需要替 </span><code style="background-color: rgba(0, 0, 0, 0.04); border-radius: 3px; box-sizing: border-box; color: inherit !important; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Random</code><span style="box-sizing: border-box;"> 物件明白指定一個種子,那就得另外想辦法了。一個通用的解決之道,是採用同步機制(synchronization)來確保每次(同一時間)只有一條執行緒能夠產生隨機數字。這部分的細節就不展開了,詳情可參考</span><a href="https://learn.microsoft.com/en-us/dotnet/api/system.random?view=net-6.0#ThreadSafety" rel="noopener" style="background-color: transparent; box-sizing: border-box; color: #337ab7; text-decoration-line: none;" target="_blank"><span style="box-sizing: border-box;">微軟文件:Random Class</span></a><span style="box-sizing: border-box;"> 或 Andrew Lock 的文章:</span><a href="https://andrewlock.net/building-a-thread-safe-random-implementation-for-dotnet-framework/" rel="noopener" style="background-color: transparent; box-sizing: border-box; color: #337ab7; text-decoration-line: none;" target="_blank"><span style="box-sizing: border-box;">Working with System.Random and threads safely in .NET Core and .NET Framework</span></a><span style="box-sizing: border-box;">。</span></p><p style="background-color: white; box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span style="letter-spacing: 0.35px;">Happy coding!</span></p></span><div>
</div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-58490357683838990842022-09-03T01:12:00.010+08:002022-09-03T09:28:55.505+08:00C# 例外處理(Exception Handling)<p>這是我在大改版《<a href="https://leanpub.com/csharp-kungfu" target="_blank">C# 本事</a>》電子書的時候所增加的一章。從 C# 7 到目前的 C# 10 都沒有對例外處理增加新功能,故本文對 C# 熟手來說,大概僅止於複習吧。若能有溫故知新的效果,那也不錯。</p><span><a name='more'></a><br /></span><div><span><p aria-describedby="popover235408" class="part" data-endline="3" data-original-title="" data-position="30" data-size="0" data-startline="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;" title=""><span data-position="30" data-size="152" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">程式執行的過程中,任何時候都有可能發生意外,也就是正常情況下不會發生的情形,或者一些難以預料的狀況。這些意外的狀況,統稱為「例外」(exceptions)。現代程式語言大多有提供一組語法來專門處理例外狀況,以便適當劃分「正常流程」與「例外流程」的程式碼,避免它們彼此糾纏,也讓程式碼更容易閱讀、更好維護。</span></p><p></p><p class="part" data-endline="5" data-position="184" data-size="0" data-startline="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="184" data-size="23" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">在 C# 中,用來處理例外的關鍵字主要有四個:</span><code data-position="208" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">try</code><span data-position="212" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">、</span><code data-position="214" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="220" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">、</span><code data-position="222" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="230" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">、和 </span><code data-position="234" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">throw</code><span data-position="240" data-size="50" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">。稍後介紹相關語法時,還會提到例外篩選器(exception filters)、釋放資源的標準寫法(</span><code data-position="291" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">using</code><span data-position="297" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 陳述句)、基礎類別 </span><code data-position="309" data-size="16" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">System.Exception</code><span data-position="326" data-size="20" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 與其家族成員,以及例外處理的實務建議。</span></p><p class="part" data-endline="7" data-position="348" data-size="0" data-startline="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="348" data-size="13" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">先來看一個典型的寫法:用 </span><code data-position="362" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">try</code><span data-position="366" data-size="34" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 陳述式包住一個正常流程的程式碼區塊,並將處理例外狀況的程式碼寫在 </span><code data-position="401" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="407" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊。像這樣:</span></p><pre class="part in-view" data-endline="18" data-position="417" data-startline="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">try
{
... // 正常流程的程式碼
}
catch
{
... // 意外狀況發生時,會執行此區塊的程式碼
}
</code></pre><p class="part in-view" data-endline="20" data-position="504" data-size="0" data-startline="20" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="504" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">這個 </span><code data-position="508" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">try...catch</code><span data-position="520" data-size="13" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 的寫法可以這樣來理解:「</span><span data-position="533" data-size="0" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; font-weight: 700;"><span data-position="535" data-size="2" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">嘗試</span></span><span data-position="539" data-size="41" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">執行一些程式碼,看看會不會出錯。萬一真的發生意外狀況,就中斷目前的正常流程,並且把</span><span data-position="580" data-size="0" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; font-weight: 700;"><span data-position="582" data-size="2" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">捕捉</span></span><span data-position="586" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">到的例外交給 </span><code data-position="594" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="600" data-size="10" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊來繼續處理。」</span></p><p class="part in-view" data-endline="22" data-position="612" data-size="0" data-startline="22" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="612" data-size="51" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">剛才的範例只是例外處理的一種常見形式。實務上,我們還可以更細緻地針對不同類型的例外來個別處理。像這樣:</span></p><pre class="part in-view" data-endline="41" data-position="665" data-startline="24" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">try</span>
{
... <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 正常流程的程式碼</span>
}
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> (FileNotFoundException fileEx)
{
... <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 處理「檔案找不到」的意外狀況。</span>
}
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> (SqlException sqlEx)
{
... <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 處理資料庫相關操作的意外狀況。</span>
}
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> (Exception ex)
{
... <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 前面幾張「網子」都沒抓到的漏網之魚,全都在此處理。</span>
}
</code></pre><p class="part in-view" data-endline="43" data-position="902" data-size="0" data-startline="43" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="902" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">由上例可知,</span><code data-position="909" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">try</code><span data-position="913" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊之後可以有多個 </span><code data-position="925" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="931" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊,而且每個 </span><code data-position="941" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="947" data-size="18" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊可以指定要捕捉的特定例外類型。</span></p><p class="part in-view" data-endline="45" data-position="967" data-size="0" data-startline="45" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="967" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">值得一提的是 </span><code data-position="975" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="981" data-size="62" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊之間的順序:愈特殊的例外類型要寫在愈上面,而愈是一般的例外類型應該寫在愈下面。以上面的例子來說,如果把第一個和第三個 </span><code data-position="1044" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1050" data-size="22" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊的位置交換,在語意上就會變成優先捕捉 </span><code data-position="1073" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Exception</code><span data-position="1083" data-size="26" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 類型的例外;由於 .NET 中的任何例外類型都是 </span><code data-position="1110" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Exception</code><span data-position="1120" data-size="17" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 的後代,所以任何例外狀況都會被 </span><code data-position="1138" data-size="20" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch (Exception ex)</code><span data-position="1159" data-size="15" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 捕捉到,那麼寫在下方的其他 </span><code data-position="1175" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1181" data-size="28" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊就等於完全沒作用了。不過你也不用太擔心自己會寫錯 </span><code data-position="1210" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1216" data-size="32" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 的順序,因為當編譯器發現這種情況時就會出現編譯失敗的錯誤訊息。</span></p><h2 class="part in-view" data-endline="47" data-id="catch-子句的幾種寫法" data-startline="47" id="catch-子句的幾種寫法" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-bottom: 1px solid rgb(238, 238, 238); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a class="anchor hidden-xs" href="https://hackmd.io/0f94rvP5Qpu6-izAYcUvFg#catch-%E5%AD%90%E5%8F%A5%E7%9A%84%E5%B9%BE%E7%A8%AE%E5%AF%AB%E6%B3%95" smoothhashscroll="" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: transparent; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="catch-子句的幾種寫法"><span class="octicon octicon-link" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; -webkit-font-smoothing: antialiased; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><code data-position="1254" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: inherit; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1260" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 子句的幾種寫法</span></h2><p class="part in-view" data-endline="49" data-position="1270" data-size="0" data-startline="49" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><code data-position="1271" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1277" data-size="38" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 子句有多種寫法,前面範例所展示的只是其中一種。方便起見,這裡再重複貼一次:</span></p><p aria-describedby="popover948220" class="part" data-endline="49" data-original-title="" data-position="1270" data-size="0" data-startline="49" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;" title=""></p><p></p><pre class="part" data-endline="56" data-position="1317" data-startline="51" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> (FileIOException ex) <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 捕捉到例外時,把例外物件指派給變數 ex,</span>
{ <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 以便稍後可以存取例外的屬性或方法。 </span>
Console.WriteLine(ex.Message);
}
</code></pre><p class="part" data-endline="58" data-position="1483" data-size="0" data-startline="58" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="1483" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">如果在 </span><code data-position="1488" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1494" data-size="26" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊中不需要操作例外物件,則可以省略變數,像這樣:</span></p><pre class="part" data-endline="65" data-position="1522" data-startline="60" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> (FileIOException) <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 僅指定欲捕捉的例外類型。</span>
{
...
}
</code></pre><p class="part" data-endline="67" data-position="1596" data-size="0" data-startline="67" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="1596" data-size="24" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">又或者寫得更簡略,就如本節最早的範例所用的寫法:</span></p><pre class="part" data-endline="74" data-position="1622" data-startline="69" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 捕捉任何例外,且不在乎何種例外。</span>
{
...
}
</code></pre><h3 class="part" data-endline="76" data-id="例外篩選器" data-startline="76" id="例外篩選器" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 1.25em; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px;"><a class="anchor hidden-xs" href="https://hackmd.io/0f94rvP5Qpu6-izAYcUvFg#%E4%BE%8B%E5%A4%96%E7%AF%A9%E9%81%B8%E5%99%A8" smoothhashscroll="" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: transparent; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="例外篩選器"><span class="octicon octicon-link" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; -webkit-font-smoothing: antialiased; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><span data-position="1689" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">例外篩選器</span></h3><p class="part" data-endline="78" data-position="1696" data-size="0" data-startline="78" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="1696" data-size="2" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">在 </span><code data-position="1699" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1705" data-size="10" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 子句中還可以加上 </span><code data-position="1716" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">when</code><span data-position="1721" data-size="14" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 子句來指定篩選條件,例如:</span></p><pre class="part" data-endline="85" data-position="1737" data-startline="80" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{
...
}
</code></pre><p class="part" data-endline="87" data-position="1841" data-size="0" data-startline="87" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="1841" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">在上面的範例中,如果 </span><code data-position="1853" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">try</code><span data-position="1857" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊拋出了 </span><code data-position="1865" data-size="12" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">WebException</code><span data-position="1878" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">,被 </span><code data-position="1882" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1888" data-size="17" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊捕捉到,在此同時,就會檢查 </span><code data-position="1906" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">when</code><span data-position="1911" data-size="26" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 子句中的篩選條件是否為真。若結果為真,才會執行該 </span><code data-position="1938" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1944" data-size="20" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊的程式碼;若結果為假,則會跳過此 </span><code data-position="1965" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1971" data-size="14" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊,並繼續往下處理其他 </span><code data-position="1986" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="1992" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 子句(如果有的話)。</span></p><p class="part" data-endline="89" data-position="2005" data-size="0" data-startline="89" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="2005" data-size="54" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">加入了例外篩選條件之後,我們將能夠更細緻地處理各種不同的例外狀況,甚至還可以對同一種例外捕捉兩次以上,例如:</span></p><pre class="part" data-endline="100" data-position="2061" data-startline="91" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{
...
}
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> (WebException ex) when (ex.Status == WebExceptionStatus.SendFailure)
{
...
}
</code></pre><h2 class="part" data-endline="102" data-id="finally-子句:最終必定會執行" data-startline="102" id="finally-子句:最終必定會執行" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-bottom: 1px solid rgb(238, 238, 238); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a class="anchor hidden-xs" href="https://hackmd.io/0f94rvP5Qpu6-izAYcUvFg#finally-%E5%AD%90%E5%8F%A5%EF%BC%9A%E6%9C%80%E7%B5%82%E5%BF%85%E5%AE%9A%E6%9C%83%E5%9F%B7%E8%A1%8C" smoothhashscroll="" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: transparent; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="finally-子句:最終必定會執行"><span class="octicon octicon-link" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; -webkit-font-smoothing: antialiased; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><code data-position="2258" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: inherit; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="2266" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 子句:最終必定會執行</span></h2><p class="part" data-endline="104" data-position="2279" data-size="0" data-startline="104" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><code data-position="2280" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">try</code><span data-position="2284" data-size="15" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊底下一定要跟著至少一個 </span><code data-position="2300" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="2306" data-size="12" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊,要不然就必須是 </span><code data-position="2319" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="2327" data-size="32" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊——其作用為:無論執行過程是否發生例外狀況,最終都要執行 </span><code data-position="2360" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="2368" data-size="19" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊中的程式碼。一種常見的寫法是在 </span><code data-position="2388" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="2396" data-size="43" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊中撰寫資源回收的工作,例如關閉檔案、關閉網路連線或資料庫連線等等。參考以下範例:</span></p><pre class="part" data-endline="125" data-position="2441" data-startline="106" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">StreamReader reader = null;
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">try</span>
{
reader = <span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">new</span> StreamReader(<span class="hljs-string" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #df5000;">"app.config"</span>);
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">var</span> content = reader.ReadToEnd();
Console.WriteLine(content);
}
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> (FileIOException ex)
{
Console.WriteLine(ex.Message);
}
finally
{
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">if</span> (reader != null)
{
reader.Dispose();
}
}
</code></pre><p class="part" data-endline="127" data-position="2757" data-size="0" data-startline="127" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="2757" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">程式碼說明:</span></p><ol class="part" data-endline="132" data-position="2765" data-size="0" data-startline="129" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 16px; margin-top: 0px; padding-left: 2em;"><li data-endline="129" data-position="2768" data-size="0" data-startline="129" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><span data-position="2768" data-size="47" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">第 1~7 行:開啟一個文字檔,然後讀取檔案內容,再輸出至螢幕。如果一切順利,跳至第 3 步(</span><code data-position="2816" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="2824" data-size="17" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊)。如果讀取檔案的過程發生 </span><code data-position="2842" data-size="15" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">FileIOException</code><span data-position="2858" data-size="14" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 類型的例外,則跳至相應的 </span><code data-position="2873" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="2879" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊。</span></li><li data-endline="130" data-position="2887" data-size="0" data-startline="130" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; padding-top: 0.25em;"><span data-position="2887" data-size="12" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">第 8~11 行:處理 </span><code data-position="2900" data-size="15" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">FileIOException</code><span data-position="2916" data-size="24" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 類型的例外,並將錯誤訊息輸出至螢幕,然後進入 </span><code data-position="2941" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="2949" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊。</span></li><li data-endline="132" data-position="2957" data-size="0" data-startline="131" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; padding-top: 0.25em;"><span data-position="2957" data-size="30" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">第 12~18 行:關閉先前已經開啟的檔案,並釋放相關資源。</span></li></ol><p class="part" data-endline="133" data-position="2989" data-size="0" data-startline="133" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="2989" data-size="14" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">有兩個細節值得提出來說一下。</span></p><p class="part" data-endline="135" data-position="3005" data-size="0" data-startline="135" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="3005" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">首先,</span><code data-position="3009" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">try</code><span data-position="3013" data-size="40" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊中的任何一行程式碼都有可能發生我們意想不到的錯誤,而當某一行程式碼出錯時,</span><code data-position="3054" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">try</code><span data-position="3058" data-size="30" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊中剩下的程式碼就不會被執行到,因為此時會立刻跳到某個 </span><code data-position="3089" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="3095" data-size="14" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊(如果有的話);待某 </span><code data-position="3110" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="3116" data-size="13" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊執行完畢,便會執行 </span><code data-position="3130" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="3138" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊的程式碼。</span></p><p class="part" data-endline="137" data-position="3148" data-size="0" data-startline="137" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="3148" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">其次,如果 </span><code data-position="3155" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">try</code><span data-position="3159" data-size="31" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊中的程式碼在執行過程中發生例外,而那個例外並沒有被任何 </span><code data-position="3191" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="3197" data-size="16" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊捕捉並處理,則程式會跳到 </span><code data-position="3214" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="3222" data-size="164" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊,等到此區塊的程式碼執行完畢,那個尚未被處理的例外依然存在,所以會被拋到上一層程式碼區塊。如果上一層程式碼也沒有捕捉並處理那個例外,則又會繼續往上拋,直到它被處理為止。要是某個例外狀況一路往上拋,直到應用程式最外層的主程式區塊都沒有被處理,此時應用程式可能就會意外中止,而使用者可能會看到程式底層 API 拋出的錯誤訊息。</span></p><p class="part" data-endline="139" data-position="3388" data-size="0" data-startline="139" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="3374" data-size="41" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">簡言之,只要例外沒有被目前的程式區塊所捕捉並處理,就一定會往外層拋。因此,如果在 </span><code data-position="3416" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="3422" data-size="38" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊中的程式碼又引發了例外,那個新產生的例外自然也會往外層拋。實務上,在 </span><code data-position="3461" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="3467" data-size="24" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊中處理例外時,應注意避免再度引發新的例外(除非是刻意為之)。</span></p><h3 class="part" data-endline="141" data-id="使用-using-來釋放資源" data-startline="141" id="使用-using-來釋放資源" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 1.25em; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px;"><a class="anchor hidden-xs" href="https://hackmd.io/0f94rvP5Qpu6-izAYcUvFg#%E4%BD%BF%E7%94%A8-using-%E4%BE%86%E9%87%8B%E6%94%BE%E8%B3%87%E6%BA%90" smoothhashscroll="" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: transparent; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="使用-using-來釋放資源"><span class="octicon octicon-link" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; -webkit-font-smoothing: antialiased; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><span data-position="3497" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">使用 </span><code data-position="3501" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: inherit; margin: 0px; padding: 0.2em 0px;">using</code><span data-position="3507" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 來釋放資源</span></h3><p class="part" data-endline="143" data-position="3529" data-size="0" data-startline="143" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="3515" data-size="10" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">上一個小節的範例中,</span><code data-position="3526" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="3534" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊裡面使用了 </span><code data-position="3544" data-size="16" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">reader.Dispose()</code><span data-position="3561" data-size="87" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 來釋放檔案資源,這是 .NET 程式很常見的寫法。其實不只是檔案,其他像是網路連線、資料庫連線等等,這些都是屬於無法由 .NET 執行環境自動回收的資源(即所謂的 </span><span data-position="3648" data-size="0" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; font-weight: 700;"><span data-position="3650" data-size="19" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">unmanaged resources</span></span><span data-position="3671" data-size="72" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">),故必須在寫程式的時候明確呼叫特定的方法來釋放資源。由於釋放資源是相當常見的工作,於是 .NET 基礎類別庫定義了一個介面來規範一致的寫法: </span><code data-position="3744" data-size="18" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">System.IDisposable</code><span data-position="3763" data-size="23" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">。此介面只定義了一個方法,即用來釋放資源的 </span><code data-position="3787" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Dispose</code><span data-position="3795" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">。</span></p><p class="part" data-endline="145" data-position="3812" data-size="0" data-startline="145" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="3798" data-size="62" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">也就是說,只要是用來處理 unmanaged resources 的類別(例如剛才提到的檔案、資料庫連線等等),都必須實作 </span><code data-position="3861" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">IDisposable</code><span data-position="3873" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">,以便在適當時機呼叫 </span><code data-position="3885" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Dispose</code><span data-position="3893" data-size="34" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 方法來釋放物件所占用的資源。於是,我們經常會撰寫類似底下的程式碼:</span></p><pre class="part" data-endline="161" data-position="3943" data-startline="147" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">StreamReader reader = null;
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">try</span>
{
reader = <span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">new</span> StreamReader(<span class="hljs-string" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #df5000;">"app.config"</span>);
...
}
finally
{
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">if</span> (reader != null)
{
reader.Dispose();
}
}
</code></pre><p class="part" data-endline="163" data-position="4131" data-size="0" data-startline="163" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="4131" data-size="51" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">上述寫法堪稱標準範本,只是程式碼超過 10 行,顯得過於繁瑣、笨重。因此,C# 提供了一個簡便的語法:</span><code data-position="4183" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">using</code><span data-position="4189" data-size="23" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 宣告,讓我們能夠用一行程式碼就完成上述工作:</span></p><pre class="part" data-endline="167" data-position="4214" data-startline="165" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">using</span> <span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">var</span> reader = <span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">new</span> <span class="hljs-type" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">StreamReader</span>(<span class="hljs-string" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #df5000;">"app.config"</span>);
</code></pre><p class="part" data-endline="169" data-position="4286" data-size="0" data-startline="169" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="4286" data-size="26" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">或者你也可以加上一對大括弧來明確限定物件的存活範圍:</span></p><pre class="part" data-endline="176" data-position="4314" data-startline="171" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">using</span> (<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">var</span> reader = <span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">new</span> <span class="hljs-type" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">StreamReader</span>(<span class="hljs-string" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #df5000;">"app.config"</span>))
{
... <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 在此區塊內皆可使用 reader 物件</span>
}
</code></pre><p class="part" data-endline="178" data-position="4422" data-size="0" data-startline="178" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="4422" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">一旦程式離開了 </span><code data-position="4431" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">using</code><span data-position="4437" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊,</span><code data-position="4442" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">reader</code><span data-position="4449" data-size="15" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 就會被自動釋放(自動呼叫其 </span><code data-position="4465" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Dispose</code><span data-position="4473" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 方法)。</span></p><p class="part" data-endline="180" data-position="4480" data-size="0" data-startline="180" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="4480" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">那麼,如果使用了單行 </span><code data-position="4492" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">using</code><span data-position="4498" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 宣告的語法,</span><code data-position="4506" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">reader</code><span data-position="4513" data-size="33" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 物件又是何時釋放呢?答案是:在它所屬的區塊結束時。請看以下範例:</span></p><pre class="part" data-endline="188" data-position="4548" data-startline="182" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">if</span> (File.Exists(<span class="hljs-string" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #df5000;">"app.config"</span>))
{
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">using</span> <span class="hljs-title" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #795da3;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">var</span></span> <span class="hljs-title" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #795da3;">reader</span> = <span class="hljs-title" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #795da3;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">new</span></span> <span class="hljs-title" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #795da3;">StreamReader</span>("<span class="hljs-title" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #795da3;">app</span>.<span class="hljs-title" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #795da3;">config</span>");
Console.WriteLine(reader.ReadToEnd());
}
</code></pre><p class="part" data-endline="190" data-position="4702" data-size="0" data-startline="190" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="4702" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">當程式離開 </span><code data-position="4709" data-size="2" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">if</code><span data-position="4712" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊時,</span><code data-position="4718" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">reader</code><span data-position="4725" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 便會自動釋放。</span></p><h2 class="part" data-endline="192" data-id="throw:拋出例外" data-startline="192" id="throw:拋出例外" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-bottom: 1px solid rgb(238, 238, 238); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a class="anchor hidden-xs" href="https://hackmd.io/0f94rvP5Qpu6-izAYcUvFg#throw%EF%BC%9A%E6%8B%8B%E5%87%BA%E4%BE%8B%E5%A4%96" smoothhashscroll="" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: transparent; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="throw:拋出例外"><span class="octicon octicon-link" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; -webkit-font-smoothing: antialiased; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><code data-position="4739" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: inherit; margin: 0px; padding: 0.2em 0px;">throw</code><span data-position="4745" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">:拋出例外</span></h2><p class="part" data-endline="194" data-position="4752" data-size="0" data-startline="194" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="4752" data-size="19" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">我們已經看過例外處理的三個主要關鍵字:</span><code data-position="4772" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">try</code><span data-position="4776" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">、</span><code data-position="4778" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="4784" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">、</span><code data-position="4786" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">finally</code><span data-position="4794" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">。現在要介紹最後一個:</span><code data-position="4806" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">throw</code><span data-position="4812" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">。</span></p><p class="part" data-endline="196" data-position="4815" data-size="0" data-startline="196" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="4815" data-size="29" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">當你在程式的某處需要引發例外來中斷正常流程時,便可以使用 </span><code data-position="4845" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">throw</code><span data-position="4851" data-size="12" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 來拋出一個例外。範例:</span></p><pre class="part" data-endline="207" data-position="4865" data-startline="198" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">void <span class="hljs-constructor" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">Print(<span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">string</span> <span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">name</span>)</span>
{
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">if</span> (name<span class="hljs-operator" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> == </span>null)
{
throw <span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">new</span> <span class="hljs-constructor" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">ArgumentNullException(<span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">nameof</span>(<span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">name</span>)</span>);
}
Console.<span class="hljs-constructor" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">WriteLine(<span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">name</span>)</span>;
}
</code></pre><p class="part" data-endline="209" data-position="5032" data-size="0" data-startline="209" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><code data-position="5033" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Print</code><span data-position="5039" data-size="10" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 方法會先檢查參數 </span><code data-position="5050" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">name</code><span data-position="5055" data-size="22" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 是否為 null,如果是的話,便拋出一個 </span><code data-position="5078" data-size="21" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">ArgumentNullException</code><span data-position="5100" data-size="33" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 類型的例外,讓呼叫端知道此函式堅決不接受傳入的參數為 null。</span></p><p class="part" data-endline="211" data-position="5136" data-size="0" data-startline="211" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="5136" data-size="21" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">第 3~6 行程式碼也可以用一行解決:</span><code data-position="5158" data-size="40" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">ArgumentNullException.ThrowIfNull(name);</code><span data-position="5199" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">。</span></p><p class="part" data-endline="213" data-position="5202" data-size="0" data-startline="213" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="5202" data-size="12" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">順便提及,當你需要拋出 </span><code data-position="5215" data-size="22" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">NullReferenceException</code><span data-position="5238" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">,除了用 </span><code data-position="5244" data-size="34" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">throw new NullReferenceException()</code><span data-position="5279" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">,也可以這樣寫:</span></p><pre class="part" data-endline="217" data-position="5289" data-startline="215" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">throw</span> <span class="hljs-literal" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #0086b3;">null</span>;
</code></pre><h3 class="part" data-endline="219" data-id="再度拋出例外" data-startline="219" id="再度拋出例外" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 1.25em; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px;"><a class="anchor hidden-xs" href="https://hackmd.io/0f94rvP5Qpu6-izAYcUvFg#%E5%86%8D%E5%BA%A6%E6%8B%8B%E5%87%BA%E4%BE%8B%E5%A4%96" smoothhashscroll="" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: transparent; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="再度拋出例外"><span class="octicon octicon-link" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; -webkit-font-smoothing: antialiased; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><span data-position="5326" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">再度拋出例外</span></h3><p class="part" data-endline="221" data-position="5334" data-size="0" data-startline="221" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="5334" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">你也可以在 </span><code data-position="5341" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="5347" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊裡面使用 </span><code data-position="5356" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">throw</code><span data-position="5362" data-size="13" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 來將目前的例外再度拋出:</span></p><pre class="part" data-endline="233" data-position="5377" data-startline="223" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">try
{
<span class="hljs-built_in" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #005cc5;">DoSomething</span>();
}
catch (Exception ex)
{
<span class="hljs-built_in" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #005cc5;">Log</span>(ex); <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 把當前的例外資訊寫入 log。</span>
throw; <span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 再次拋出同一個例外。</span>
}
</code></pre><p class="part" data-endline="235" data-position="5508" data-size="0" data-startline="235" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="5508" data-size="29" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">這裡有個細節值得一提:在上述範例中,如果把倒數第二行寫成 </span><code data-position="5538" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">throw ex</code><span data-position="5547" data-size="18" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 也可以通過編譯,但其作用稍微不同:</span></p><ul class="part" data-endline="239" data-startline="237" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 16px; margin-top: 0px; padding-left: 2em;"><li data-endline="237" data-position="5569" data-size="0" data-startline="237" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><span data-position="5569" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">單單寫 </span><code data-position="5574" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">throw</code><span data-position="5580" data-size="51" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 會保留原本的堆疊追蹤資訊(stack trace)。也就是說,透過堆疊追蹤資訊,便能抓到原始的例外。</span></li><li data-endline="239" data-position="5634" data-size="0" data-startline="238" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; padding-top: 0.25em;"><span data-position="5634" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">如果寫 </span><code data-position="5639" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">throw ex</code><span data-position="5648" data-size="29" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">,則會重設堆疊追蹤資訊,這將導致呼叫端無法透過例外物件的 </span><code data-position="5678" data-size="10" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">StackTrace</code><span data-position="5689" data-size="37" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 屬性得知引發例外的源頭是哪一行程式碼。基於此緣故,通常不建議採用此寫法。</span></li></ul><p class="part" data-endline="240" data-position="5728" data-size="0" data-startline="240" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="5729" data-size="30" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">如果你想觀察兩種寫法在執行時有何差異,可用瀏覽器開啟此連結:</span><a href="https://dotnetfiddle.net/Q8MV1H" rel="noopener" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: transparent; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #337ab7; text-decoration-line: none;" target="_blank"><span data-position="5759" data-size="18" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">ThrowAndThrowEx.cs</span></a><span data-position="5811" data-size="24" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">,然後按照其中的提示來修改程式碼並觀察執行結果。</span></p><blockquote class="part in-view" data-endline="245" data-original-title="" data-startline="242" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-left: 0.25em solid rgb(221, 221, 221); box-sizing: border-box; color: #777777; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px; padding: 0px 1em;" title=""><p data-position="5836" data-size="0" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; margin: 0px 0px 16px;"><span data-position="5836" data-size="0" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; font-weight: 700;"><span data-position="5838" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">呼叫堆疊與堆疊追蹤</span></span></p><p data-position="5855" data-size="0" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; margin: 0px;"><span data-position="5855" data-size="86" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">堆疊追蹤(stack trace)是一個字串,裡面包含了當前呼叫堆疊(call stack)裡面的所有方法的名稱,以及方法所在的程式碼行號——如果編譯時有啟用除錯資訊的話。</span><br style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;" /><span data-position="5942" data-size="59" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">呼叫堆疊指的是一個記憶體區塊,其中保存了某一條執行緒當時的所有方法呼叫的資訊,例如傳入方法的參數、方法的區域變數等等。</span></p></blockquote><p class="part in-view" data-endline="247" data-position="6005" data-size="0" data-startline="247" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><br /></p><p class="part in-view" data-endline="247" data-original-title="" data-position="6005" data-size="0" data-startline="247" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;" title=""><span data-position="6005" data-size="2" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">於 </span><code data-position="6008" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="6014" data-size="31" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊中再次拋出例外時,你也可以拋出一個新的、不同類型的例外:</span></p><pre class="part in-view" data-endline="261" data-position="6047" data-startline="249" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-function" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">DateTime <span class="hljs-title" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #795da3;">StringToDate</span>(<span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><span class="hljs-built_in" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #005cc5;">string</span> input</span>)</span>
{
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">try</span>
{
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">return</span> Convert.ToDateTime(input);
}
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">catch</span> (FormatException ex)
{
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">throw</span> <span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">new</span> ArgumentException(<span class="hljs-string" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #df5000;">$"無效的引數: <span class="hljs-subst" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">{<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">nameof</span>(input)}</span>"</span>, ex);
}
}
</code></pre><p class="part in-view" data-endline="263" data-position="6281" data-size="0" data-startline="263" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="6281" data-size="15" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">請注意上述範例在拋出一個新的 </span><code data-position="6297" data-size="17" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">ArgumentException</code><span data-position="6315" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 時,有把當前的例外 </span><code data-position="6327" data-size="2" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">ex</code><span data-position="6330" data-size="29" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 傳入建構子的第二個參數,這會把當前的例外保存於新例外的 </span><code data-position="6360" data-size="14" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">InnerException</code><span data-position="6375" data-size="49" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 屬性。也就是說,在拋出新例外的同時,依然保存原始的例外資訊(也許呼叫端在診斷錯誤的時候會用到)。</span></p><p class="part" data-endline="265" data-position="6402" data-size="0" data-startline="265" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="6402" data-size="14" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">一般來說,以下幾種場合會在 </span><code data-position="6417" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">catch</code><span data-position="6423" data-size="12" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 區塊中拋出不同的例外:</span></p><ul class="part" data-endline="270" data-startline="267" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 16px; margin-top: 0px; padding-left: 2em;"><li data-endline="267" data-position="6439" data-size="0" data-startline="267" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><span data-position="6439" data-size="21" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">在處理當前的例外時,又發生了其他意外狀況。</span></li><li data-endline="268" data-position="6463" data-size="0" data-startline="268" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; padding-top: 0.25em;"><span data-position="6463" data-size="43" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">讓外層接收到更一般的例外類型,以便隱藏底層的細節(不想讓外界知道太多、避免駭客攻擊)。</span></li><li data-endline="270" data-position="6509" data-size="0" data-startline="269" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; padding-top: 0.25em;"><span data-position="6509" data-size="33" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">讓外層接收到更特殊的例外類型,以便呼叫端更明確知道發生錯誤的原因。</span></li></ul><h2 class="part" data-endline="271" data-id="Exception-類別及其家族成員" data-startline="271" id="Exception-類別及其家族成員" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-bottom: 1px solid rgb(238, 238, 238); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a class="anchor hidden-xs" href="https://hackmd.io/0f94rvP5Qpu6-izAYcUvFg#Exception-%E9%A1%9E%E5%88%A5%E5%8F%8A%E5%85%B6%E5%AE%B6%E6%97%8F%E6%88%90%E5%93%A1" smoothhashscroll="" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: transparent; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="Exception-類別及其家族成員"><span class="octicon octicon-link" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; -webkit-font-smoothing: antialiased; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><span data-position="6547" data-size="18" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">Exception 類別及其家族成員</span></h2><p class="part" data-endline="273" data-position="6567" data-size="0" data-startline="273" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><code data-position="6568" data-size="16" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">System.Exception</code><span data-position="6585" data-size="28" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 類別是所有例外的共同祖先(基礎類別),其常用的屬性有:</span></p><ul class="part" data-endline="278" data-startline="275" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 16px; margin-top: 0px; padding-left: 2em;"><li data-endline="275" data-position="6617" data-size="0" data-startline="275" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><code data-position="6618" data-size="10" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">StackTrace</code><span data-position="6629" data-size="46" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">:是一個字串,裡面包含了發生例外當下的函式呼叫堆疊(call stack)中的所有方法名稱。</span></li><li data-endline="276" data-position="6678" data-size="0" data-startline="276" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; padding-top: 0.25em;"><code data-position="6679" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Message</code><span data-position="6687" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">:描述錯誤訊息的字串。</span></li><li data-endline="278" data-position="6701" data-size="0" data-startline="277" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; padding-top: 0.25em;"><code data-position="6702" data-size="14" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">InnerException</code><span data-position="6717" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">:內部例外。如果不是 </span><code data-position="6729" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">null</code><span data-position="6734" data-size="28" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 的話,則是引發當前例外的上一個例外。此屬性的型別也是 </span><code data-position="6763" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Exception</code><span data-position="6773" data-size="21" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">,故每個內部例外都可能還有另一個內部例外。</span></li></ul><p class="part" data-endline="279" data-position="6796" data-size="0" data-startline="279" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="6796" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">這裡無法完整呈現 </span><code data-position="6806" data-size="16" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">System.Exception</code><span data-position="6823" data-size="38" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 的所有子類別與繼承關係,但是有個概略的認識還是有幫助的,如下圖(命名空間 </span><code data-position="6862" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">System</code><span data-position="6869" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 已省略):</span></p><p class="part" data-endline="279" data-position="6796" data-size="0" data-startline="279" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu-XkZ55-SqjChbuYW3wzYaYJduE-yOHrrzk_g8bkVwajs-zRaRL-EptZrTCQ9ZtwXSwn1m0PEBxz-nLA5InY8EPW7yMKTO712oRyvCvlRmSJTeckYi9Rf-xibrxI9ewKv8mWih39KQNEaK2b-qHKbl25XT1vStw-tVqTTSwnfwM3OaI1aarLK_u54/s639/exception-classdiagram.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="542" data-original-width="639" height="542" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu-XkZ55-SqjChbuYW3wzYaYJduE-yOHrrzk_g8bkVwajs-zRaRL-EptZrTCQ9ZtwXSwn1m0PEBxz-nLA5InY8EPW7yMKTO712oRyvCvlRmSJTeckYi9Rf-xibrxI9ewKv8mWih39KQNEaK2b-qHKbl25XT1vStw-tVqTTSwnfwM3OaI1aarLK_u54/w640-h542/exception-classdiagram.png" width="640" /></a></div><img alt="" data-position="6877" data-size="38" loading="lazy" src="https://hackmd.io/0f94rvP5Qpu6-izAYcUvFg" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: transparent; border: 0px; box-sizing: content-box; letter-spacing: 0.35px; max-width: 100%; vertical-align: middle;" /><p></p><p></p><p class="part" data-endline="283" data-position="6917" data-size="0" data-startline="283" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="6917" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">由上而下依序來看,</span><code data-position="6927" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Exception</code><span data-position="6937" data-size="24" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 繼承自所有 .NET 類別的共同祖先,也就是 </span><code data-position="6962" data-size="6" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Object</code><span data-position="6969" data-size="25" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">,並且有兩個子類別,分別代表例外類型的兩個大分類:</span><code data-position="6995" data-size="15" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">SystemException</code><span data-position="7011" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 和 </span><code data-position="7015" data-size="20" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">ApplicationException</code><span data-position="7036" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">。</span></p><p class="part" data-endline="285" data-position="7039" data-size="0" data-startline="285" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><code data-position="7035" data-size="15" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">SystemException</code><span data-position="7051" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 繼承自 </span><code data-position="7057" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Exception</code><span data-position="7067" data-size="16" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">,代表「系統層級」的例外,像是 </span><code data-position="7084" data-size="17" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">ArgumentException</code><span data-position="7102" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">、</span><code data-position="7104" data-size="22" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">NullReferenceException</code><span data-position="7127" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">、</span><code data-position="7129" data-size="11" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">IOException</code><span data-position="7141" data-size="50" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 等等。這些由 .NET 平台所拋出的例外,都是所謂的「系統例外」,它們通常代表無法回復的嚴重錯誤。</span></p><p class="part in-view" data-endline="287" data-position="7198" data-size="0" data-startline="287" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><code data-position="7194" data-size="20" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">ApplicationException</code><span data-position="7215" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 也是繼承自 </span><code data-position="7223" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Exception</code><span data-position="7233" data-size="73" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">,但是它沒有增加任何功能,只是單純用來「分類」,作為應用程式自訂例外的基礎類別。換言之,應用程式如果需要定義自己的例外型別,便可以優先考慮繼承自 </span><code data-position="7307" data-size="20" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">ApplicationException</code><span data-position="7328" data-size="7" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">。不過,由於 </span><code data-position="7336" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Exception</code><span data-position="7346" data-size="45" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 即代表所有執行時期發生的錯誤,所以實務上在設計自訂例外類別的時候,許多人也會直接繼承自 </span><code data-position="7392" data-size="9" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Exception</code><span data-position="7402" data-size="1" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">。</span></p><h2 class="part in-view" data-endline="289" data-id="除了拋出例外,你還有其他選擇" data-startline="289" id="除了拋出例外,你還有其他選擇" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-bottom: 1px solid rgb(238, 238, 238); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; letter-spacing: 0.35px; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em;"><a class="anchor hidden-xs" href="https://hackmd.io/0f94rvP5Qpu6-izAYcUvFg#%E9%99%A4%E4%BA%86%E6%8B%8B%E5%87%BA%E4%BE%8B%E5%A4%96%EF%BC%8C%E4%BD%A0%E9%82%84%E6%9C%89%E5%85%B6%E4%BB%96%E9%81%B8%E6%93%87" smoothhashscroll="" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: transparent; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #337ab7; float: left; line-height: 1; margin-left: -20px; padding-right: 4px; text-decoration-line: none;" title="除了拋出例外,你還有其他選擇"><span class="octicon octicon-link" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; -webkit-font-smoothing: antialiased; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: black; display: inline-block; font-family: octicons; font-size: 16px; font-stretch: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: normal; line-height: 1; text-rendering: auto; user-select: none; vertical-align: middle; visibility: hidden;"></span></a><span data-position="7408" data-size="14" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">除了拋出例外,你還有其他選擇</span></h2><p class="part in-view" data-endline="291" data-position="7429" data-size="0" data-startline="291" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="7424" data-size="53" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">本節要探討一個實務上的問題:在寫程式時,是不是碰到任何狀況都應該拋出例外?若答案為否,那我們還有什麼選擇?</span></p><p class="part in-view" data-endline="293" data-position="7484" data-size="0" data-startline="293" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="7479" data-size="51" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">比如說,當你在撰寫一個函式,你可能會在處理關鍵工作之前預先檢查各個相關的參數或變數值,確保它們不能為 </span><code data-position="7531" data-size="4" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">null</code><span data-position="7536" data-size="111" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 或者不合理的數值(例如員工薪資不可為負數),以免函式傳回不正確的結果而導致重大災難。也就是說,一旦發現任何異常狀況,就要立刻阻止程式繼續往下執行。此時你有兩個選擇:傳回某種形式的錯誤碼(可能是數字或字串),或者拋出例外。</span></p><p class="part in-view" data-endline="295" data-position="7654" data-size="0" data-startline="295" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="7649" data-size="108" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">一般而言,如果是正常流程不會出現的狀況(例如寫入檔案時,磁碟突然被拔出),或者是無法修正、無法回復的錯誤,這些情形都可以拋出例外。至於其他「情節輕微」或者容易修正的小問題,則可以考慮傳回某種旗號或錯誤碼來通知呼叫端。</span></p><p class="part in-view" data-endline="297" data-position="7764" data-size="0" data-startline="297" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="7759" data-size="52" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">如果要更貼心一點,也可以同時提供兩種口味。我們在 .NET 標準類別庫裡面也能看到這樣貼心的設計,例如 </span><code data-position="7812" data-size="3" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">int</code><span data-position="7816" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 類別的 </span><code data-position="7822" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Parse</code><span data-position="7828" data-size="10" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 方法就有兩種口味:</span></p><pre class="part in-view" data-endline="302" data-position="7845" data-startline="299" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-function" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">public</span> <span class="hljs-built_in" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #005cc5;">int</span> <span class="hljs-title" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #795da3;">Parse</span> (<span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><span class="hljs-built_in" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #005cc5;">string</span> input</span>)</span>;
<span class="hljs-function" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">public</span> <span class="hljs-built_in" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #005cc5;">bool</span> <span class="hljs-title" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #795da3;">TryParse</span>(<span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><span class="hljs-built_in" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #005cc5;">string</span> input, <span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">out</span> <span class="hljs-built_in" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #005cc5;">int</span> returnValue</span>)</span>;
</code></pre><p class="part in-view" data-endline="304" data-position="7959" data-size="0" data-startline="304" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="7959" data-size="2" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">當 </span><code data-position="7962" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">Parse</code><span data-position="7968" data-size="20" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 剖析字串的動作發生錯誤,它會拋出例外;</span><code data-position="7989" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">TryParse</code><span data-position="7998" data-size="14" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 則不會拋出例外,而是傳回 </span><code data-position="8013" data-size="5" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">false</code><span data-position="8019" data-size="29" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 來讓呼叫端根據不同的情形來執行不同的程式路徑,類似這樣:</span></p><pre class="part in-view" data-endline="316" data-position="8050" data-startline="306" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: #f7f7f7; border-radius: 3px; border: inherit; box-sizing: border-box; color: #333333; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; letter-spacing: 0.35px; line-height: 1.45; margin-bottom: 16px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 16px; word-break: break-all;"><code class="C# hljs" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background: transparent; border-radius: 3px; border: 0px; box-sizing: border-box; color: inherit; display: inline; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"><span class="hljs-built_in" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #005cc5;">int</span> result;
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">if</span> (<span class="hljs-built_in" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #005cc5;">int</span>.<span class="hljs-constructor" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">TryParse(<span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">input</span>, <span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">out</span> <span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">result</span>)</span>)
{
Console.<span class="hljs-constructor" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">WriteLine(<span class="hljs-params" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">result</span>)</span>;
}
<span class="hljs-keyword" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #a71d5d;">else</span>
{
<span class="hljs-comment" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #969896;">// 無法將字串轉成整數時,有其他應對方式。</span>
}
</code></pre><p class="part in-view" data-endline="49" data-position="1270" data-size="0" data-startline="49" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin: 0px 0px 16px;"><span data-position="1277" data-size="38" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"></span></p><p class="part in-view" data-endline="318" data-position="8192" data-size="0" data-startline="318" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 0px !important; margin-left: 0px; margin-right: 0px; margin-top: 0px; margin: 0px;"><span data-position="8192" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">相較於拋出例外,</span><code data-position="8201" data-size="8" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: rgba(0, 0, 0, 0.04); border-color: rgba(231, 231, 231, var(--tw-border-opacity)); border-radius: 3px; box-sizing: border-box; color: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0px;">TryParse</code><span data-position="8210" data-size="74" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"> 這種傳回成功或失敗旗號的作法就像是在告訴你:「如果字串無法轉成整數也是你預期的合理狀況之一,而且你會根據不同狀況作出相應的處理,那選擇我就對了。」</span></p><p class="part in-view" data-endline="318" data-position="8192" data-size="0" data-startline="318" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 0px !important; margin-left: 0px; margin-right: 0px; margin-top: 0px; margin: 0px;"><span data-position="8210" data-size="74" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><br /></span></p><p class="part in-view" data-endline="318" data-position="8192" data-size="0" data-startline="318" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 0px !important; margin-left: 0px; margin-right: 0px; margin-top: 0px; margin: 0px;"><span data-position="8210" data-size="74" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">Keep coding! 💪</span></p><p class="part in-view" data-endline="318" data-position="8192" data-size="0" data-startline="318" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 0px !important; margin-left: 0px; margin-right: 0px; margin-top: 0px; margin: 0px;"><span data-position="8210" data-size="74" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;"><br /></span></p><p class="part in-view" data-endline="318" data-position="8192" data-size="0" data-startline="318" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; background-color: white; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box; color: #333333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang TC", "Microsoft JhengHei", 微軟正黑, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; letter-spacing: 0.35px; margin-bottom: 0px !important; margin-left: 0px; margin-right: 0px; margin-top: 0px; margin: 0px;"><span data-position="8210" data-size="74" style="--tw-border-opacity: 1; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-inset: var(--tw-empty, ); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; border-color: rgba(231, 231, 231, var(--tw-border-opacity)); box-sizing: border-box;">💬備註:</span><span style="background-color: transparent;">這是我在改版《</span><a href="https://leanpub.com/csharp-kungfu" style="background-color: transparent;" target="_blank">C# 本事</a><span style="background-color: transparent;">》電子書的時候所增加的一章。此次改版幅度頗大,所以我是在一個獨立分支裡面進行改版工作。也因為這個緣故,目前新增的章節還不會出現在 Leanpub 與其他電子書平台。等到所有章節內容大致底定,會在這裡和<a href="https://www.facebook.com/huanlin.notes" target="_blank">臉書</a>發布消息。歡迎關注、點讚、糾錯與建議。</span></p></span></div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-38388447979194704652022-07-12T04:24:00.012+08:002022-07-12T13:24:54.214+08:00.NET MAUI 簡介:從 .NET 歷史說起<p><a href="https://www.huanlintalk.com/2022/07/a-first-look-at-net-blazor-hybrid-app.html" target="_blank">上一篇文章</a>(以及示範影片)快速體驗了一下 .NET MAUI app 執行於 Android 模擬器的過程,這次要從 .NET 歷史來介紹 MAUI。</p><span><a name='more'></a></span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3V9TpzmxH3nayORFWdWMUov1Gjh6wdGvIbUibDdl_WV28f3bR8-QxcbWbq6BYkQ64cbm94jx25Xr_GPQt_NhH1ROn6GXqb0hg7qpgP4MJ0nNPLn4GXGF3AbPv9f1jG8Etd0olwav7bZ-OBuqLHqLqMPz8DbxJeAtcJdFYlrCIJNCXcHpPANO_oM7Y/s586/dotnet-maui-logo.png" style="margin-left: 1em; margin-right: 1em;"><br /><img border="0" data-original-height="300" data-original-width="586" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3V9TpzmxH3nayORFWdWMUov1Gjh6wdGvIbUibDdl_WV28f3bR8-QxcbWbq6BYkQ64cbm94jx25Xr_GPQt_NhH1ROn6GXqb0hg7qpgP4MJ0nNPLn4GXGF3AbPv9f1jG8Etd0olwav7bZ-OBuqLHqLqMPz8DbxJeAtcJdFYlrCIJNCXcHpPANO_oM7Y/s16000/dotnet-maui-logo.png" /></a></div><br />
<div>印象中,「寫一次,執行於多種平台」最早是由一手打造 Java 的昇陽公司(Sun Microsystems)提出來的。在那之前,程式設計師都是直接面對底層作業系統的 API 來寫程式。後來,Java 在程式設計師與作業系統之間提供了一層轉換機制,讓 Java 應用程式在執行時通過這個轉換層來對應至底層作業系統的 API。這個執行時期的轉換層叫做 JVM(Java Virtual Machine)。如此一來,只要各家作業系統上面都有運行 JVM,開發人員就可以忽略底層作業系統的差異,只需要專注在與 JVM 搭配的 API 程式庫,從而實現了「寫一次,執行於各家作業平台」的目標。</div><br /><p>關於跨平台程式設計,微軟也有提出自己的答案,那便是 2000 年推出的 .NET Framework。</p><p><br /></p><p>與 Java 類似,.NET Framework 也有一個執行時期的轉換層,叫做 CLR(Common Language Runtime),以及與之搭配的一套 API 程式庫,叫做 BCL(Base Class Library)。.NET 開發人員通常只針對 BCL 來寫程式,而只在某些特定場合才需要直接存取 Windows 作業系統層次的 API。與 Java 不同的是,.NET Framework 只是讓開發人員能夠更輕鬆地設計出「跨不同 Windows 版本」的應用程式,而不能跨到 Windows 之外的作業系統。換言之,.NET Framework 是「Windows 限定」。此限制一直到 .NET Core 出現才開始獲得一些解放。</p><p><br /></p><p>儘管 .NET Core 應用程式可在 Windows、Mac、和 Linux 作業系統上運行,但是侷限在命令列和伺服器端的應用(例如網站和 Web APIs),因為 GUI(圖形使用者介面)的部分並未實現跨平台。其實在更早之前,已經有一群人為了讓 .NET 應用程式能夠跨平台而努力,其結晶便是開源專案 Mono。</p><p><br /></p><p>早期版本的 Mono 可以說是開源版本的 .NET Framework,它有自己的 C# 編譯器、Mono 版本的 ASP.NET 和 Windows Forms。經過一些版本演進,Mono 不僅支援 Windows、Mac、Linux,還納入了 iOS、Android 等行動作業系統。後來,參與 Mono 專案的一些開發人員組成了一家公司:Xamarin,而這個團隊打造了一套跨平台 UI 的框架,叫做 Xamarin Forms。以往,由於 iOS 和 Android 平台的差異,開發人員必須學習兩套原生 APIs 和特定平台的程式語言,而 Xamarin 讓 .NET 開發人員能夠使用自己熟悉的 C# 語言來開發跨 iOS 與 Android 行動裝置的應用程式,在開發成本與效率方面頗具優勢。</p><p><br /></p><blockquote><p>Xamarin 公司是在 2014 年推出 Xamarin Forms,然後在 2016 年併入微軟旗下。</p></blockquote><p><br /></p><p>於是乎,.NET 生態系開始複雜起來,我們有 .NET Framework、.NET Core、Mono、和 Xamarin。它們彼此有相似與重疊之處,也各有不同程度的差異。為了簡化、避免混淆,微軟開始在框架演進過程中對名稱做了調整,從 .NET 5 開始捨棄「Core」,單純叫「.NET」,而早期的「.NET Framework」就繼續讓它維持「Windows 限定」的角色。</p><p><br /></p><p>基本上,.NET 5 可以說是 .NET Framework 與 .NET Core 的合體,不僅可以開發「跨平台的」ASP.NET 應用程式,也可以開發 Windows Forms 和 WPF 應用程式。然而,.NET 5 的 GUI 框架依然只能運行在 Windows 上面,就像拼圖缺了一塊……直到 .NET 6 出現。</p><p><br /></p><p>在 .NET 6,連 GUI 的部分都能夠跨平台了。擔此重任的,是一個叫做 MAUI 的 UI 框架。儘管 MAUI 骨子裡有 Xamarin Forms 的基因,然而作為後繼者,它有著新的立足點和更遠大的目標,為實現「寫一次,執行於多種平台」願景持續添加柴火。</p><p><br /></p><h2 style="text-align: left;">什麼是 .NET MAUI?</h2><p>MAUI 的全名是 .NET Multi-platform App UI,它是微軟的開源專案,一個新的跨平台 UI 框架,可執行於 Windows、macOS、iOS、和 Android。筆者寫下這段文字時,MAUI 尚未支援 Linux UI 應用程式,而根據<a href="https://docs.microsoft.com/zh-tw/dotnet/maui/supported-platforms" target="_blank">微軟文件</a>,Linux UI 的部分將會由社群協助打造,而不會由微軟官方支援。</p><blockquote><p>註:.NET 已經支援 Linux 命令列與服務類型的應用程式。</p></blockquote><p><br /></p><p>也就是說,當我們用 C# 開發一個 MAUI 應用程式,它將能夠執行於所有 MAUI 支援的作業平台。UI 的部分,可以使用 XAML(一種標記語言),也可以用 C# 或 F# 程式碼來建構使用者介面。</p><p><br /></p><p>底下是 .NET MAUI 應用程式的架構圖:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7dEaMU3FqHjKOicGZp7viPHeaubPe2UfKpLx9T1kwWJ3J_3tuMCjTbxuJFwROAabVrX0jhs_wVnj7l2V8l2L7kAF60a505vE94KPl8sHVcR5nX0keCDDKTpQQbZQhZGY2ZiOhnNkavsCPxPW8OabMAZef-fbkIhFcg5QEajkYQMVvvUIIpVJ1390l/s749/2022-07-12_04-25-34.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="463" data-original-width="749" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7dEaMU3FqHjKOicGZp7viPHeaubPe2UfKpLx9T1kwWJ3J_3tuMCjTbxuJFwROAabVrX0jhs_wVnj7l2V8l2L7kAF60a505vE94KPl8sHVcR5nX0keCDDKTpQQbZQhZGY2ZiOhnNkavsCPxPW8OabMAZef-fbkIhFcg5QEajkYQMVvvUIIpVJ1390l/w640-h396/2022-07-12_04-25-34.png" width="640" /></a></div><br /><p>依序由上而下來看,最上面那層是我們的應用程式,下面緊接著 .NET MAUI API。圖中那個較短的箭頭表示我們只要對 .NET MAUI 這層 API 來寫程式,便可支援底層的四種作業平台。這是因為 .NET MAUI 在背後替我們處理了 API 對應的繁複工作。圖的第三層可以看到,MAUI 往下對應的分別是 .NET 提供的四種特定平台的函式庫:.NET for Android、.NET for iOS、.NET for macOS、和 WinUI。圖中那個比較長的箭頭所代表的意思是,若有需要,開發人員也可以直接使用這四種特定平台的 .NET 函式庫來分別設計各平台的 UI——不過,這種作法即表示我們必須個別維護特定平台的程式碼。</p><p><br /></p><p>在四種特定平台的 .NET 函式庫之下,則是跨平台的 .NET BCL(Base Class Library)。BCL 提供了許多常用的基礎類別,像是串列、泛型等等。在 BCL 之下,則是負責應用程式執行時期相關工作的 .NET runtime。這層 runtime 有兩種版本,一個是 Mono,負責處理 Android、iOS、和 macOS 平台,另一個則是 WinRT,負責 Windows 平台。</p><p><br /></p><p>以上所述,是從比較高的位置來俯瞰 .NET MAUI 應用程式的架構。再往細一點說,.NET MAUI app 可以在 Windows 或 Mac 電腦上面進行開發,而在編譯時,我們撰寫的 C# 程式碼將會被編譯成各原生平台的檔案格式:</p><p></p><ul style="text-align: left;"><li><b>Android</b>:C# 程式碼會被編譯成中介語言(IL),然後在 app 啟動時立即(just-in-time;JIT)編譯成 Android 平台的原生組件。</li><li><b>iOS</b>:C# 程式碼會直接編譯成原生的 ARM 代碼。相較於剛才說的 JIT 模式,這種直接編譯成原生代碼的作法叫做 AOT(ahead-of-time)編譯。</li><li><b>macOS</b>:透過蘋果公司提供的 Mac Catalyst 來將我們的 iOS app 轉換成可執行於 macOS 平台的版本。</li><li><b>Windows</b>:使用 WinUI 函式庫(寫作當下的版本是 WinUI 3)來建立 Windows 平台上的原生 app。</li></ul><p></p><blockquote><p>備註:若有需要,也可以對 Android app 使用 AOT 編譯。</p></blockquote><p>請注意,如果要編譯 iOS 和 macOS 版本的 MAUI 應用程式,我們手邊還得有一台 Mac 電腦才行。目前我尚未實際做過這個部分(Mac 電腦還沒買啊!),不過從<a href="https://docs.microsoft.com/en-us/dotnet/maui/ios/pair-to-mac" target="_blank">微軟的技術文件</a>可以大概知道,Visual Studio 2022 是透過它的 Pair to Mac 功能來連線到 Mac 電腦,並執行該電腦上的 Mac 建置工具來編譯 iOS app。</p><p><br /></p><h2 style="text-align: left;">結語</h2><p>.NET MAUI 的出現,對於維護十幾年 Windows Forms 應用程式的老園丁,同時也是行動 app 苦手的我來說,簡直就像隧道盡頭冒出一道光(<a href="https://www.youtube.com/watch?v=nBK4AH8ZozU" target="_blank">就是這個光!</a>)。加上 Blazor Hybrid 技術,讓 GUI 應用程式也能嵌入 Web UI,為 .NET 開發人員提供了更多選項,可針對不同需求來選擇合適的解決方案。</p><p><br /></p><p>或許更重要的是,作為 Xamarin Forms 的繼承者,MAUI 已經成為 .NET 家族的成員,而不是獨立存在的函式庫或軟體開發工具箱。這多少也意味著微軟對 .NET MAUI 未來持續發展的承諾吧。</p><p><br /></p><p>(太棒了,要買一台 Mac 電腦!😅)</p><p><br /></p><h2 style="text-align: left;">參考資料</h2><p></p><ul style="text-align: left;"><li>微軟文件:<a href="https://docs.microsoft.com/zh-tw/dotnet/maui/what-is-maui" target="_blank">什麼是 .NET MAUI?</a></li><li>微軟文件:<a href="https://docs.microsoft.com/zh-tw/dotnet/maui/supported-platforms" target="_blank">.NET MAUI 應用程式支援的平臺</a></li><li>微軟文件:<a href="https://docs.microsoft.com/en-us/dotnet/maui/ios/pair-to-mac" target="_blank">Pair to Mac for iOS development</a></li></ul><p></p>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-16733142966547529812022-07-09T22:16:00.026+08:002022-12-27T12:21:26.873+08:00.NET Blazor Hybrid app with MAUI 初體驗<p>看完 Daniel Roth 的 Youtube 影片,我動手做了一遍,然後寫了一點筆記,也錄製了一個實作練習的影片。</p><span><a name='more'></a></span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaAK1N0-qyK031bXOfzzLTcjQKu1BKK5vm2i-gaj1ov1EbSH75QSjJGa0sEGvl1ylwFwxXlQkdz0cVFzFIv9VpuNF4EASBUnkeeeRjYTGrxRd3WCrLbSOVCR1Rurtvmt8j3M1XAR9KieIWKYUrzoJlkPojajUUqlw0o_A77UJAFo7srtHhTCyus7Ym/s800/2022-07-09_22-58-54b.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="532" data-original-width="800" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaAK1N0-qyK031bXOfzzLTcjQKu1BKK5vm2i-gaj1ov1EbSH75QSjJGa0sEGvl1ylwFwxXlQkdz0cVFzFIv9VpuNF4EASBUnkeeeRjYTGrxRd3WCrLbSOVCR1Rurtvmt8j3M1XAR9KieIWKYUrzoJlkPojajUUqlw0o_A77UJAFo7srtHhTCyus7Ym/w640-h426/2022-07-09_22-58-54b.png" width="640" /></a></div><h2 style="text-align: left;">前言</h2><p>之所以想試一下 Blazor Hybrid app,是因為我有一些已經運行十年以上的 Windows 應用程式,其中有的是商業軟體,客戶也還在使用。如果未來有機會把這些 Windows 應用程式改寫成跨平台的版本,在手機上面也能跑的話,那真是挺酷的啊!</p><p><br /></p><p>如果你也有興趣玩一下 Blazor Hybrid app,可以看 Daniel Roth 的影片:<a href="https://www.youtube.com/watch?v=7BDBLyADq7s" target="_blank">ASP.NET Community Standup - Let's build an app with .NET MAUI and Blazor</a>。我也只是看完影片之後依樣畫葫蘆做了一遍,然後再做第二遍、第三遍,最後再把「還算順暢」的練習過程錄下來,剪輯成影片。</p><p><br /></p><p>我的影片是用 Movavi 錄製和剪輯,由於對這套剪輯軟體還不那麼熟悉,以至於最終成品有一點殘影和卡頓的現象。此外,影片中沒有旁白講解,我是以加註圖文的方式來替影片加上重點解說。無論如何,如果你想看看,可以點以下連結:</p><p><br /></p><p>👉 Youtube 影片:<span face="Roboto, Noto, sans-serif" style="background-color: white; color: #0d0d0d; font-size: 15px; white-space: pre-wrap;"><a href="https://youtu.be/zZdhP4qTxTc" target="_blank">Hello, Blazor Hybrid with NET MAUI</a></span></p><blockquote><p>有關 Movavi 螢幕錄製結果出現殘影與卡頓現象的問題和解決方法,我已經寫在另一篇筆記:<a href="https://www.huanlintalk.com/2022/06/from-camtasia-to-movavi.html" target="_blank">從 Camtasia 換成 Movavi</a>。</p></blockquote><p><br /></p><p>.NET 演進速度很快,當你看到我的影片時,說不定 MAUI 和 Blazor 又有了一些比較大的改變。文章的好處是可以隨意修改更新,所以我還是把一些筆記整理成文章,補影片的不足,且未來如果需要更新或補充也比較方便。</p><p><br /></p><p>不過,這篇文章的實作步驟並沒有我的影片中示範的那樣複雜,二者主要差異是:<b><span style="color: #660000;">影片中會示範如何讓 MAUI Blazor App 專案和 Blazor Server App 專案共用同一個 Razor Class Library 專案中的 UI 元件,這篇文章則是偏重於 Android 模擬器的相關事項。</span></b></p><p><br /></p><p>閒話說得多了,進入正題吧。</p><h2 style="text-align: left;">Blazor Hybrid 簡介</h2><p>Blazor Hybrid 技術的主要目的是結合 desktop app 和 web app 二者的優勢,例如將 desktop app 的其中一部份 UI 換成 Blazor(即 Web UI)。如此一來,UI 設計有了更大的彈性,同時又能存取裝置的原生功能與資源。</p><p><br /></p><div><div>為了方便理解,可以粗略地把 Blazor Hybrid app 理解為「執行於此電腦或行動裝置的 Blazor Server app」。</div></div><div><br /></div><div>關於 Blazor Hybrid 的幾個 facts:</div><div><ul style="text-align: left;"><li>不涉及 WebAssembly 或 server endpoint。</li><li>不涉及 browser sandbox,也就是說,Blazor Hybrid app 並不是執行於瀏覽器中。</li><li>Blazor Hybrid 可搭配三種口味的 UI 框架:MAUI、WPF、Windows Forms。Blazor 對這三種框架都有提供 BlazorWebView 控制項來呈現 Razor 元件。換言之,即使是老舊的 Windows Forms 應用程式,也能藉由 Blazor Hybrid 技術來添加新的 Web UI。</li></ul>如果需要更詳細的介紹,可以讀一下微軟文件:<a href="https://docs.microsoft.com/zh-tw/aspnet/core/blazor/hybrid/?view=aspnetcore-6.0" target="_blank">ASP.NET Core Blazor Hybrid</a>。<br /><h2 style="text-align: left;">練習</h2><div>接下來的練習並未涉及程式撰寫,主要還是為了體驗一下 MAUI app 部署執行於 Windows 和 Android 裝置的過程。</div><div><br /></div><div><div>我的作業環境與開發工具如下:</div><div><ul><li>Windows 11 22H2</li><li>Visual Studio 2022 Preview (Version 17.3.0 Preview 2.0)</li><li>.NET SDK 6.0.400-preview.22301.10</li><li>.NET SDK 7.0.100-preview.5.22307.18</li></ul></div></div><div><br /></div><div>在動手練習之前,請先確定你的 Visual Studio 2022 有安裝 Blazor Hybrid App 的相關元件與專案範本。做法是開啟 Visual Studio Installer,修改安裝選項,確保其中的「.NET Multi-Platform App UI 開發」選項有打勾。如下圖:</div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbrgHF8bb3-Vc0-n0hiUL30LO6mR8ocbZu90vSh0ceMUXLB7SKOEs63aUcr2lDPls4BHP-LCXdwo_czBjo2xtevSQG2wgFJsE6QMFaVpGm9fXGuWwbWp-rfWmMFz9cSP2GAucWk9UACqz6Xz48mGFSqJPg9BmHuIaTodHk24Pb5y9sm2vCY9-ZodCO/s1280/2022-07-05_01-50-33.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="799" data-original-width="1280" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbrgHF8bb3-Vc0-n0hiUL30LO6mR8ocbZu90vSh0ceMUXLB7SKOEs63aUcr2lDPls4BHP-LCXdwo_czBjo2xtevSQG2wgFJsE6QMFaVpGm9fXGuWwbWp-rfWmMFz9cSP2GAucWk9UACqz6Xz48mGFSqJPg9BmHuIaTodHk24Pb5y9sm2vCY9-ZodCO/w640-h400/2022-07-05_01-50-33.png" width="640" /></a></div><br /><div>接著開啟 Visual Studio 2022,建立專案時選擇 .NET MAUI Blazor App 範本。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_w9J9yX4gsw44CyfGHODD2zav-7FqJKLB6M1KIFQbYew4NXO310vH65X2Mg66S2liSoN3fnLkmvs1S8OH9POS5blRoNsKPJpvgZQHZkjt2GMCxeTqhAeKhgqJsE7k7Lhdv8Mijdvq-HkO7N4sUghJylJi000ItaeTnojJAsA66skwgSgfHUtEwn6p/s1014/2022-07-05_01-46-27.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="675" data-original-width="1014" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_w9J9yX4gsw44CyfGHODD2zav-7FqJKLB6M1KIFQbYew4NXO310vH65X2Mg66S2liSoN3fnLkmvs1S8OH9POS5blRoNsKPJpvgZQHZkjt2GMCxeTqhAeKhgqJsE7k7Lhdv8Mijdvq-HkO7N4sUghJylJi000ItaeTnojJAsA66skwgSgfHUtEwn6p/w640-h426/2022-07-05_01-46-27.png" width="640" /></a></div><br /><div><br /></div><div>一切採用預設值,專案名稱會是 MauiApp1。在不修改任何程式碼的情況下,先把程式執行起來看看。</div><h3 style="text-align: left;">執行於 Windows 平台</h3><div>在 Visual Studio 執行應用程式時,有多種作業平台供我們選擇。先選擇「Windows Machine」,看看應用程式執行於 Windows 平台的樣子。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGFFTIm4wqNLsF47EwHDb0I7Y_liQDILOcmeNTShGSj5ReaW8CicwjBw07_usJyUAVn5raEiwLN6K7WtKCXk0Glwa-n59B4kvnGO7m07O_b7jxOvgQlwGqCPc9_PKGoPteHqX2iqneRKaRxhYqLSyILQqsb-sr0ICJUDheOvsntm0eazvrOON1O9wf/s884/2022-07-05_02-34-57.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="389" data-original-width="884" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGFFTIm4wqNLsF47EwHDb0I7Y_liQDILOcmeNTShGSj5ReaW8CicwjBw07_usJyUAVn5raEiwLN6K7WtKCXk0Glwa-n59B4kvnGO7m07O_b7jxOvgQlwGqCPc9_PKGoPteHqX2iqneRKaRxhYqLSyILQqsb-sr0ICJUDheOvsntm0eazvrOON1O9wf/w640-h282/2022-07-05_02-34-57.png" width="640" /></a></div><br /><div>執行結果:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgon8XLXWKzjKaOW5QdKJbYJOXaeR3_-XE7ywNmeTAxxDSftPwOvj3RJAxYHHiZQh0NWxSucIlkTPDl_46CxfEdegWI1z6_KXjTIbvp_ED74fn6xPlNtqd8wBDVciMlAvuMHF-HWTvBJ21G5S7w8jwF5P_xOuD1OeMEXDcUfXNac_r0eocRbH2PEo-C/s1023/2022-07-05_03-19-57.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="479" data-original-width="1023" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgon8XLXWKzjKaOW5QdKJbYJOXaeR3_-XE7ywNmeTAxxDSftPwOvj3RJAxYHHiZQh0NWxSucIlkTPDl_46CxfEdegWI1z6_KXjTIbvp_ED74fn6xPlNtqd8wBDVciMlAvuMHF-HWTvBJ21G5S7w8jwF5P_xOuD1OeMEXDcUfXNac_r0eocRbH2PEo-C/w640-h300/2022-07-05_03-19-57.png" width="640" /></a></div><br /><div>接著嘗試以 Android Emulator 來執行看看。</div><div><h3 style="text-align: left;">執行於 Android 模擬器</h3><div>如果是第一次執行 Android 模擬器,會需要接受 Android 平台的相關授權條款。同意所有條款之後,會出現 Android 裝置管理員視窗,要求建立預設的 Android 裝置,如下圖:</div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir5DdX9gre1rg1RgBR9YVdwT4JLcfCMbnSDPI9Il0bjuXQNgYt7e1mGdmLgitIBcEa5Jb3qtThahl9NV5tdexIGhvF6pDtB8zVhX2NOoIS42VCsUErAYvYoVUbRo1PVg5_pXL9RdaeZyHAjduThhl99BgzTpJ6hX6bOyLGNEXKvjESYhoMWpRCZGBT/s896/2022-07-05_02-39-56.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="479" data-original-width="896" height="342" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir5DdX9gre1rg1RgBR9YVdwT4JLcfCMbnSDPI9Il0bjuXQNgYt7e1mGdmLgitIBcEa5Jb3qtThahl9NV5tdexIGhvF6pDtB8zVhX2NOoIS42VCsUErAYvYoVUbRo1PVg5_pXL9RdaeZyHAjduThhl99BgzTpJ6hX6bOyLGNEXKvjESYhoMWpRCZGBT/w640-h342/2022-07-05_02-39-56.png" width="640" /></a></div><br /><div>點 Create 按鈕之後,會開啟 Android 裝置管理員,並下載裝置所需的檔案:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhagL8iDfIB_JTZouoaa_XYq_8EVQSUX52LenhZH__G1K3jBJ88C4oeLb-B8hJ7CATcEQnVSm80TZWrY1rmZYpb7wX1ryoEWr_2HFUl0A9JoUtCzbzpNpHBzXE9ZhvBFDlQ7J595dSYsW931e2ochSyY6FN5f4hZUV9IEDvZbyi9tE1HkTin-19O-N-/s894/2022-07-05_02-42-14.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="413" data-original-width="894" height="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhagL8iDfIB_JTZouoaa_XYq_8EVQSUX52LenhZH__G1K3jBJ88C4oeLb-B8hJ7CATcEQnVSm80TZWrY1rmZYpb7wX1ryoEWr_2HFUl0A9JoUtCzbzpNpHBzXE9ZhvBFDlQ7J595dSYsW931e2ochSyY6FN5f4hZUV9IEDvZbyi9tE1HkTin-19O-N-/w640-h296/2022-07-05_02-42-14.png" width="640" /></a></div><br /><div>下載完成後,點一下「啟動」,如果看到如下圖的提示訊息,即表示你的 Windows 系統尚未安裝 Hyper-V 相關服務。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDsYeDmIXGMZBnkoUhEblZ96ECMwKxCx3M4ymbJSPvlgGkBL3JvDM8XYmrZWzngfcnYXKmT-xBAuk_kE10ZQg4-bUoRghSxUgYA-tVj2XQrkMRai3MhlBe6sYyHms_CfFd2KznR-8W51Z8CUfdGj2l3sizpi94Zji24w7cs_gUtzn4QZNd4PmxjVT2/s894/2022-07-05_02-45-56.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="403" data-original-width="894" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDsYeDmIXGMZBnkoUhEblZ96ECMwKxCx3M4ymbJSPvlgGkBL3JvDM8XYmrZWzngfcnYXKmT-xBAuk_kE10ZQg4-bUoRghSxUgYA-tVj2XQrkMRai3MhlBe6sYyHms_CfFd2KznR-8W51Z8CUfdGj2l3sizpi94Zji24w7cs_gUtzn4QZNd4PmxjVT2/w640-h288/2022-07-05_02-45-56.png" width="640" /></a></div><br /><div><br /></div><div>將目前的對話窗關閉,然後關閉 Visual Studio。接著按 Win 鍵,在搜尋框裡面輸入「Windows 功能」便可找到控制台的「開啟或關閉 Windows 功能」。進入此功能設定視窗後,參考下圖來安裝 Hyper-V 相關服務與元件。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH87PDrgGFml8nH3832WbmFBVMjtUnWowyZ88-NLE45vsiUKjNyFmdm1Y3k2wv0loIS-pzaBtEDzlsz3p-tj9knDOQjFTsr-mTtIauvXl8e7NqaLxOInjiVXDS7Af56cBqjpcsTOa3XqKp9gBQkFYqLH8CMK5bb4gbKGeD4enM-ZcqjXbDoxtNKTkU/s630/2022-07-05_02-48-18.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="630" data-original-width="486" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH87PDrgGFml8nH3832WbmFBVMjtUnWowyZ88-NLE45vsiUKjNyFmdm1Y3k2wv0loIS-pzaBtEDzlsz3p-tj9knDOQjFTsr-mTtIauvXl8e7NqaLxOInjiVXDS7Af56cBqjpcsTOa3XqKp9gBQkFYqLH8CMK5bb4gbKGeD4enM-ZcqjXbDoxtNKTkU/s16000/2022-07-05_02-48-18.png" /></a></div><br /><div>安裝 Hyper-V 需要重新開機。完成後,再次開啟先前的 Visual Studio 專案,然後以 Android 模擬器執行。部署過程大約要等個幾分鐘,如果一切順利,應該就能看到如下圖的執行結果:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy08Rn6pFrklGkSftsE7RZYcLXIEPSUs4uvdvjbSAAWZZcOsueimOWVrr3PXmjYpgmANryoE4ZbkanYmQPdZ4p5rLt05IPwYwxCJ9To2VwFr-jBTqcqAJxBzB_5efr_AFTOzw3rwzpbSnePXlLU9SeBTS9-U0OlElaIF9N_YGNK1eoNmxkEmBFiVlK/s691/2022-07-05_03-15-24.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="691" data-original-width="414" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy08Rn6pFrklGkSftsE7RZYcLXIEPSUs4uvdvjbSAAWZZcOsueimOWVrr3PXmjYpgmANryoE4ZbkanYmQPdZ4p5rLt05IPwYwxCJ9To2VwFr-jBTqcqAJxBzB_5efr_AFTOzw3rwzpbSnePXlLU9SeBTS9-U0OlElaIF9N_YGNK1eoNmxkEmBFiVlK/s16000/2022-07-05_03-15-24.png" /></a></div><br /><div><br /></div><div><div>在專案已經完成編譯的情況下,從開始按 F5 直到出現上圖的畫面,我用馬錶測量的結果是大約花了 2 分鐘。</div><h3 style="text-align: left;">Android 模擬器相關設定</h3><div>前面提過的 Android 裝置管理員,你也可以從 Visual Studio 的 Tools | Android 選單裡面找到:</div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiamt6ootVb3ILpnE8uHUJLG9CObAbr0AAoAzd8SS1Ijft6CRMs1iCD9DVhNO59jKNdmbk6RjMx6rOmACu8qZaYTwMcMF2H_DFmihyAsc5DibphX8ZK0X9SKx5Tan6ubHbdp1D1cOBJ1YptF1f4VrtdU-F3ZRd6Ki09TGbGcmTMr177HCP-J8XokWAV/s910/2022-07-05_11-59-58.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="320" data-original-width="910" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiamt6ootVb3ILpnE8uHUJLG9CObAbr0AAoAzd8SS1Ijft6CRMs1iCD9DVhNO59jKNdmbk6RjMx6rOmACu8qZaYTwMcMF2H_DFmihyAsc5DibphX8ZK0X9SKx5Tan6ubHbdp1D1cOBJ1YptF1f4VrtdU-F3ZRd6Ki09TGbGcmTMr177HCP-J8XokWAV/w640-h226/2022-07-05_11-59-58.png" width="640" /></a></div><br /><div><br /></div><div><div>上圖的 Android 選單裡面還有一個「Android SDK Manager」,點擊此項,便可管理目前已安裝的 Android SDK 版本和相關工具,如下圖。</div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy3bUbwQnAlJ1ixFnX5p6T1uKSempVRJ6X3gORd_BOlRD-w5mN19cQKswwzTswkUOxXn7IaG6-iWsThPzJlGrKZZigPLa_JtC8wK69glHzxbqNA0qKBeexrOsqaYXohSfChwQBw3cboyfYGsqaYW6HC5HQh3_LndPnuZSZK1xeyMzUHTwkxOHVtl39/s793/2022-07-05_12-08-03.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="582" data-original-width="793" height="470" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy3bUbwQnAlJ1ixFnX5p6T1uKSempVRJ6X3gORd_BOlRD-w5mN19cQKswwzTswkUOxXn7IaG6-iWsThPzJlGrKZZigPLa_JtC8wK69glHzxbqNA0qKBeexrOsqaYXohSfChwQBw3cboyfYGsqaYW6HC5HQh3_LndPnuZSZK1xeyMzUHTwkxOHVtl39/w640-h470/2022-07-05_12-08-03.png" width="640" /></a></div><div><br /></div><div>注意圖中的視窗底部有個提示「1 Update Available」,顯示有新版本的 SDK 工具可以安裝,點一下即可進行更新。</div><div><h3 style="text-align: left;">Android 模擬器的加速器</h3><div>我看到有些教學影片和文章說 Visual Studio 2022 的安裝選項裡面的「個別元件」裡面的「Intel Hardware Accelerated Execution Manager (HAXM) (本機安裝)」要打勾,如下圖:</div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsVW31RPLEg8fVPN_qPoujfVkwR-oej52WflMS4cyvEbRUvLEb2mM5EgZvtArDDyePU0u2WzU9RpDhUSVmQ2IovQeEsy4B-qLwNOfoBuJTkhZCCMEP3exSXuV5ZbEYcv6396Qxg7dKZDRQ-cdbUm8waBf0AA9eG6K9X02DZ9JPNh26s3myCLs3qe9Y/s1280/2022-07-05_13-04-06.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="720" data-original-width="1280" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsVW31RPLEg8fVPN_qPoujfVkwR-oej52WflMS4cyvEbRUvLEb2mM5EgZvtArDDyePU0u2WzU9RpDhUSVmQ2IovQeEsy4B-qLwNOfoBuJTkhZCCMEP3exSXuV5ZbEYcv6396Qxg7dKZDRQ-cdbUm8waBf0AA9eG6K9X02DZ9JPNh26s3myCLs3qe9Y/w640-h360/2022-07-05_13-04-06.png" width="640" /></a></div><br /><div>但我的 Windows 環境沒有安裝此元件,依然可順利將 MAUI 應用程式執行於 Android 模擬器中。也許是因為我的機器有啟用 WHPX 的緣故?</div><div><br /></div><div>關於 WHPX,微軟網站上有一份文件,標題是「Hyper-V & HAXM (模擬器效能的硬體加速)」,這裡摘錄其中一段文字:</div><div><br /></div><blockquote><div><div>下列虛擬化技術可用於加速 Android Emulator:</div><div><ol style="text-align: left;"><li>Microsoft 的 Hyper-V 與 Windows Hypervisor 平台 (WHPX)。<br /> Hyper-V 是Windows虛擬化功能,可讓您在實體主機電腦上執行虛擬化的電腦系統。</li><li>Intel Hardware Accelerated Execution Manager (HAXM)。<br /> HAXM 是執行 Intel CPU 之電腦的虛擬化引擎。</li></ol></div><div>若要在 Windows 中擁有最佳體驗,建議您使用 WHPX 來加速 Android 模擬器。 如果您的電腦上沒有 WHPX,則可以使用 HAXM。 如果符合下列準則,Android Emulator 會自動使用硬體加速:</div><div><ul style="text-align: left;"><li>您的開發電腦有提供並已啟用硬體加速。</li><li>模擬器正在執行為 x86 型虛擬裝置建立的系統映像。</li></ul></div></div></blockquote><p><br /></p><p>其中提到,「<b><span style="color: #660000;">若要在 Windows 中擁有最佳體驗,建議您使用 WHPX 來加速 Android 模擬器。</span></b>」</p><h3 style="text-align: left;">Android 模擬器疑難雜症</h3><p>練習過程中,我在 Android 模擬器的部分碰到了一些奇怪的問題。有時候,Android 模擬器的畫面全黑,不知道發生什麼事,只是反覆關閉模擬裝置再重新啟動,問題便消失了。</p><p><br /></p><p>還有一個詭異的狀況:在 Visual Studio 中以 Android 模擬器來執行 MAUI app 時,會卡在部署程序,就像是進入無窮迴圈那樣。起初,我以為只是多等一會兒,也許三、五分鐘後,總會跑完,結果喝完一杯咖啡再回來看,依然還在跑:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitNxJ7p-6yJNk5h76sNhpPvPkQvtN6UStX_qp-PsHa8dqIvRlJt1XoudSBVkZQJ8-hs9hWuPGnrvwnxlDFLo3I-66eKG0ULIXoFCMQdNdketYysXktjBXi9QEkksEWwd51JU0sOAUoJ3FQGHcwOy1kevX7ExQJ3sgIHQQOWvatV3n4yJrpSX1vuCxa/s734/2022-07-09_18-39-37.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="326" data-original-width="734" height="284" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitNxJ7p-6yJNk5h76sNhpPvPkQvtN6UStX_qp-PsHa8dqIvRlJt1XoudSBVkZQJ8-hs9hWuPGnrvwnxlDFLo3I-66eKG0ULIXoFCMQdNdketYysXktjBXi9QEkksEWwd51JU0sOAUoJ3FQGHcwOy1kevX7ExQJ3sgIHQQOWvatV3n4yJrpSX1vuCxa/w640-h284/2022-07-09_18-39-37.gif" width="640" /></a></div><br /><p>微軟網站上面有一份文件:<a href="https://docs.microsoft.com/zh-tw/xamarin/android/get-started/installation/android-emulator/troubleshooting?pivots=windows" target="_blank">Android Emulator 疑難排解</a>,裡面列出了十幾種狀況和排除方法,但似乎沒有一個跟我碰到的情況符合。</p><p><br /></p><p>最終我是這樣解決的:從 Visual Studio 選單點選 Tools > Android > Android Device Manager,在此裝置管理員視窗中把目前唯一使用的裝置(Pixel 5 - API 31)刪除,然後重新建立一個相同的裝置。</p><p><br /></p><p>重新建立 Android 裝置後,MAUI App 專案便又可以順利部署執行於 Android 模擬器了。</p><h2 style="text-align: left;">感想</h2><p>做完這些練習,勉強算是稍微體驗了「寫一次,執行於各平台」的好處,只是我沒有蘋果手機和平板,目前也沒有強烈動機去測試 iOS 平台。</p><p><br /></p><p>接下來,可以試著動手修改一些程式碼,讓它更接近實務上開發的專案:加入一個 Razor Component Library 專案來撰寫共用的程式碼和 UI 元件,並且在其他應用程式專案中使用那些共用元件,就像本文開頭提及的影片當中所示範的那樣。</p><p><br /></p><div>然後,我得想一下手邊現有的 Windows Forms app,要先挑哪一個來動刀,以及要怎麼修改比較好:直接用 MAUI 把既有的 UI 全部改頭換面嗎?嗯,光用想的就覺得挺累。或者,運用 Blazor Hybrid 技術,在我的 Windows Forms 裡面內嵌網頁?恐怕還得再做一些實驗,才好判斷。</div><div><br /></div><div>除此之外,每次部署程式到 Android 模擬器要花兩分多鐘的時間,似乎不是很有效率啊。看來硬體升級的日子也不遠了。</div><div><br /></div><div>投資,都是投資啊。</div><h2 style="text-align: left;">延伸閱讀</h2><div><ul style="text-align: left;"><li><a href="https://docs.microsoft.com/zh-tw/aspnet/core/blazor/hosting-models" target="_blank">ASP.NET Core Blazor 裝載模型</a> (有助於評估選擇口味)</li><li><a href="https://docs.microsoft.com/zh-tw/aspnet/core/blazor/hybrid/tutorials" target="_blank">ASP.NET Core Blazor Hybrid 教學課程</a></li></ul></div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-24434992203829493312022-07-09T16:16:00.018+08:002022-07-10T00:13:45.028+08:00使用 for 與 forfiles 命令來篩選多種檔案類型<p>記一個小技巧:搭配 for 命令來讓 forfiles 篩選多種檔案類型。</p><span><a name='more'></a></span><p><br /></p><p>Windows 的 <a href="https://docs.microsoft.com/zh-tw/windows-server/administration/windows-commands/forfiles" target="_blank">forfiles 命令</a>有一個 /M 參數可以指定搜尋檔案時的遮罩,以便篩選檔案名稱,但是它允許一個遮罩,例如以下命令將列出當前目錄下的所有 *.docx 類型的檔案名稱:</p><p><br /></p><p><span style="font-family: Consolas;">forfiles /M *.docx </span></p><p><br /></p><p>實際應用時,比如說,我可能想要用這個命令來建立一個檔名列表 filelist.txt,然後把這個 filelist .txt 喂給 7-zip 來執行壓縮,以便執行一些日常的簡易備份工作。</p><p><br /></p><p>為了產生這個「需要備份的檔名列表」,在單純使用 forfiles 命令的情況下,批次檔案中必須寫一堆相似的命令,像這樣:</p>
<br />
<script src="https://gist.github.com/huanlin/e0e57c6902cedbbc2d9229f8b4290ad9.js?file=forfiles.cmd"></script>
<br />除了剛才提到的 /M 參數,這次還加上了兩個參數:<br />
<ul style="text-align: left;"><li>/S 代表要搜尋子目錄。</li><li>/D 代表要指定日期篩選條件。上述範例的 "+2021/01/01" 意思是只撈 2021/01/01 那天以及之後有修改的檔案。<a href="https://docs.microsoft.com/zh-tw/windows-server/administration/windows-commands/forfiles" target="_blank">官方文件</a>有更詳細的說明與範例。</li></ul>
<br />如果搭配 <a href="https://docs.microsoft.com/zh-tw/windows-server/administration/windows-commands/for" target="_blank">for 命令</a>,上面一堆重複的命令就可以縮減為一行。以下範例為了排版美觀而刻意寫成兩行命令:<br />
<br />
<script src="https://gist.github.com/huanlin/e0e57c6902cedbbc2d9229f8b4290ad9.js?file=for-forfiles.cmd"></script>
<br />有兩個地方要特別注意:<div><ol style="text-align: left;"><li>如果是把 for 命令寫在批次檔(.bat)裡面,那麼上面範例中的 <span style="font-family: Consolas;"><b>%m</b></span> 要寫成 <span style="font-family: Consolas;"><b>%%m</b></span>,也就是用兩個連續的 % 符號。</li><li>for 命令的 <set> 參數,也就是上面範例中的 <span style="font-family: Consolas;"><b>(.txt, .pdf, .xls,....)</b></span>,裡面的檔案類型不可加上 * 號,否則 for 命令會把它們當成檔案來處理。在這個範例當中,我們需要在 for 的集合參數中使用單純字串,而不希望把它當成檔案來處理。在第 4 行,也就是 for 命令的 do 子句當中,會再加上 * 號來組成檔名遮罩。</li></ol><div><br /><div><div>需要備份的檔名列表建立好之後,便可以喂給壓縮工具,例如:</div><div><br /></div><div><span style="font-family: Consolas;">"c:\Program Files\7-Zip\7z" a -r -y backup.7z @filelist.txt</span></div><div><br /><div>完成!</div></div></div></div></div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0tag:blogger.com,1999:blog-4500363753981919783.post-24621305076217036382022-06-25T02:30:00.046+08:002022-07-10T00:17:45.455+08:00從 Camtasia 換成 Movavi一直以來,我都是使用 TechSmith 的 Snagit 和 Camtasia 來處理抓圖和影音編輯的工作。這兩套軟體在各方面的功能都剛好夠用,可滿足我製作教學影片的需求。這篇筆記要說的是我後來為什麼改用 Movavi<span face=""Noto Sans CJK TC", 微軟正黑體, sans-serif" style="background-color: white; color: #333333; font-size: 16px;">(讀作「抹-va-vi」)</span>。<span><a name='more'></a></span><blockquote><div>文中提及的軟體產品,如果沒有特別指名版本,即代表 Camtasia 2021、Snagit 2021、Filmora 11、Movavi 2022。</div></blockquote><div><div><h2 style="text-align: left;">Camtasia 2021 與 Windows 11 的相容問題</h2><div>我最早使用的 Camtasia 版本是 2019,之後升級至 2020,再到我目前使用的 2021 版。原本在 Windows 10 上面,使用 Camtasia 2021 並未碰到任何狀況,但自從我升級作業系統至 Windows 11,Camtasia 2021 就出現嚴重的問題:當我在編輯影片時,經常會出現影片突然無聲的情況。確切來說,當我把播放頭(play head)移到影音軌的最前面,此時預覽影片就能聽到聲音,可是如果跳到影片後面某個地方開始播放,就會完全靜音。這種狀況,可以說根本無法順利編輯影片。 </div><div><br /></div><div>後來,我在 TechSmith 官方論壇上面找到有人提問,帖子的標題是:<a href="https://support.techsmith.com/hc/en-us/community/posts/4411008793869-Windows-11-Camtasia-2021-No-audio-playback-after-1-minute-and-General-performance-issues" target="_blank">Windows 11/Camtasia 2021 - No audio playback after 1 minute and General performance issues</a>。帖子裡面描述的狀況跟我碰到的一模一樣,而且經過其他人的分享,確認了問題的原因:Windows 11 如果有加入 Insider Program,就會出現這個狀況。也就是說,如果重新安裝 Windows 11,回到一般沒有 Insider Program 的環境,Camtasia 2021 就不會出現這個狀況。可是我不想重新安裝 Windows 11,而且我也想要繼續收到 Windows 的 Insider Beta 更新。</div></div><div><br /></div><div>原本打算等等看 Camtasia 推出新版本時,或許就能解決,但沒想到這一等就是半年,而上面那篇帖子的後續討論,也有看到其他人說 Camtasia 2022 有著相同的問題。後來,我到官網敲了克服 Live Chat,確認 Camtasia 2022 的確無法正常運作於 Windows 11 Insider Program 環境。殘念 😵💫</div><div><br /></div><div>於是,我開始尋求其他替代方案,看看有沒有功能大致相同、且價格親民的影音編輯軟體。</div><h2 style="text-align: left;">買了 Filmora 11,然後退款</h2><div>我在 Youtube 上面看到有人介紹 Wondershare 公司的影音編輯軟體 <a href="https://filmora.wondershare.com" target="_blank">Filmora</a>,乍看似乎不錯,功能挺多,有些特效也很酷,於是我直接上網購買了個人版(可永久使用的版本,非訂閱制),價格是 79.99 美金。</div><div><br /></div><div>實際使用之後,發現有個我不太喜歡的地方,而那正是我經常需要的功能:對話泡泡(英文通常叫做 callout 或者 speech bubble)。</div><div><br /></div><div>Filmora 11 內建的圖文動畫,有些是蠻酷的,例如底下這個 callout:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-N7DuHDw2gniu0nCki1Zii_Nlf1J96lDga_37wTglCoAd0QaIAOHZkn97AHneldpXlkSk8SN2Jlme7Edh20Yeefw7NZL1CbuM05Z65uqjJaG1Vn-bxbQSjJiyEc5B7Gk9CLrwYwqL6YaOE6uXIciwmATIrVfZmhPrRzTHWqsqGPntRvkmrB-v3xZw/s544/callout.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="306" data-original-width="544" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-N7DuHDw2gniu0nCki1Zii_Nlf1J96lDga_37wTglCoAd0QaIAOHZkn97AHneldpXlkSk8SN2Jlme7Edh20Yeefw7NZL1CbuM05Z65uqjJaG1Vn-bxbQSjJiyEc5B7Gk9CLrwYwqL6YaOE6uXIciwmATIrVfZmhPrRzTHWqsqGPntRvkmrB-v3xZw/w640-h360/callout.gif" width="640" /></a></div><br /><div><br /></div><div>然而,我比較需要簡單一點的對話泡泡。像上面那個文字動畫,我必須調整兩個文字框,而移動位置和調整大小的時候,也得分別去調整方框和文字物件的位置,顯得有些麻煩。</div><div><br /></div><div>那麼,Filmora 提供的 callout 有哪些呢?如下圖:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAqo2yVGXBin1sLHu9qdgfrfWXxDTW88308n72UzwQBQGaOnj772y9XE7g41ZopCzHBP9ZEI-pXVcR9mUbWboKToujtBLLXzhe5JimEa3g7PMOXcBaaIubA-o3ibUs5hiNDHsgj9ahUcGo0WB6hL1ejckxzfuxjzXAEpP_mUFxMHPGMX-BZYcVz_nY/s955/2022-06-25_00-24-37.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="589" data-original-width="955" height="395" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAqo2yVGXBin1sLHu9qdgfrfWXxDTW88308n72UzwQBQGaOnj772y9XE7g41ZopCzHBP9ZEI-pXVcR9mUbWboKToujtBLLXzhe5JimEa3g7PMOXcBaaIubA-o3ibUs5hiNDHsgj9ahUcGo0WB6hL1ejckxzfuxjzXAEpP_mUFxMHPGMX-BZYcVz_nY/w640-h395/2022-06-25_00-24-37.png" width="640" /></a></div><br /><div><br /></div><div>從上圖左邊的清單可以看到 Bubble 這個分類總共有 48 個對話泡泡樣式可供選擇。可是,每個樣式左上角有紅寶石圖案的,都是需要另外付費購買才能使用,我算了一下,真正免費提供的只有 6 個樣式。而且這 6 個樣式的對話泡泡,風格全都一樣,只是形狀不同。這裡抓其中一個來看一下實際長什麼樣子:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjjiVS2EdciE5sqTbFNsFDG0Zoka8vTb8AsNrX3sKE1OUZZletlNec7qCZK5Z62c4BITQtwR54MsJ2jrijD1lIS4HborUK4Cu1KAMI0g4TBIgs70OFqSTIh0X2tN0-6q8CbLa3jH0nNNhgqCx833npSzF0hDplpW3zkuQNOvb4CX-r_9WUHOpwikzC/s593/2022-06-25_00-31-58.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="466" data-original-width="593" height="251" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjjiVS2EdciE5sqTbFNsFDG0Zoka8vTb8AsNrX3sKE1OUZZletlNec7qCZK5Z62c4BITQtwR54MsJ2jrijD1lIS4HborUK4Cu1KAMI0g4TBIgs70OFqSTIh0X2tN0-6q8CbLa3jH0nNNhgqCx833npSzF0hDplpW3zkuQNOvb4CX-r_9WUHOpwikzC/s320/2022-06-25_00-31-58.png" width="320" /></a></div><br /><div>或許是我個人審美觀的緣故,我覺得那實在太醜了,無法接受。相較之下,Snagit 和 Camtasia 的對話泡泡就很簡單、好看(更前面那張圖裡面的紅底白字泡泡就是來自 Snagit)。</div><div><br /></div><div>雖然 Filmora 還有其他強大的功能與酷炫特效,但僅僅是一個普通的對話泡泡就得另外花錢購買才能滿足我的需求,我已經信心動搖,對其他功能與特效不敢有太多期望。</div><div><br /></div><div>後來,我又發現一個問題:Filmora 在錄製螢幕畫面的時候,沒有辦法凸顯滑鼠指標位置。在 Camtasia 裡面,我可以隨意指定某個影片段落要不要凸顯滑鼠指標,也就是滑鼠指標周圍有一圈淡黃色光暈的效果。</div><div><br /></div><div>想了一下,覺得自己應該無法忽視剛才說的那兩個問題。與其忍耐著使用,不如儘早退款,看看有沒有更合適的工具吧。於是,我到 Filmora 官網提出了退款(refund)要求。原本以為,我只買了三天便提出退款,應該符合退款相關規定吧。然而,客服拒絕了我的退款請求:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguvUgO1UObcgyPHl5-obv8WCUoaHAM3D3Z61fLNtgUF4H9eyOXcOazUSCx630X2KRG9hJD62QHUvnfpt6jA8oYWnIJTN-02j7GiafBbqxbeDg1XLUB7GVy5Q5CqRko72kGPffQH4KPRo9tqETvzrPEshz_bviGGZGgqroQDKEjRfbjuI9I7PaakSOa/s1033/2022-06-25_00-59-03.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="736" data-original-width="1033" height="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguvUgO1UObcgyPHl5-obv8WCUoaHAM3D3Z61fLNtgUF4H9eyOXcOazUSCx630X2KRG9hJD62QHUvnfpt6jA8oYWnIJTN-02j7GiafBbqxbeDg1XLUB7GVy5Q5CqRko72kGPffQH4KPRo9tqETvzrPEshz_bviGGZGgqroQDKEjRfbjuI9I7PaakSOa/w640-h456/2022-06-25_00-59-03.png" width="640" /></a></div><br /><div>客服的回覆,大意是說:「我們已經有提供試用版讓客戶在購買之前可以完整評估產品的功能,所以只有碰到無法解決的技術問題才能退款。」其中還提供了退款規定的連結。</div><div><br /></div><div>我點開<a href="https://www.wondershare.com/refund-policy.html" target="_blank">退款規定頁面</a>,仔細讀了一下,裡面羅列了好幾個狀況都是不允許退款的。其中一個不允許退款的狀況是:A 'change of mind' after purchase.(購買之後改變心意)。唉,我買東西從來都沒有先閱讀退款規定的習慣,這下可傷腦筋了。</div><div><br /></div><div>雖然我在「理」字不完全站得住腳,但又覺得,從消費者的角度來看,這退款規定似乎有點嚴苛耶。於是,抱著姑且一試的心態,我又去官網送出一則意見回饋,把身為使用者的心聲說出來。我是用英文撰寫,若以中文來說,大意是:「我了解你們的退款規定了,雖然不樂意,但如果真的不能退款,我理解而且只好接受。只能說,後悔自己沒有先閱讀退款規定。我也會跟朋友提醒,購買你們的產品之前,一定要先閱讀退款規定,以免跟我犯同樣的錯。」(最後一句頗有威脅的嫌疑?😌)</div><div><br /></div><div>隔天,我收到另一位客服的回覆:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3knPCADfno_p-rKCpemBRmEpVwA_ooKJNQR1QuJGVgkVBylSYHTpeUp_d1jemaogiGQbPAFngxVL6YL_d8ZbU1pRsrTtgmzGusJH2eCPiky5SCNSfFFvm-_M5XvBC9AD5MVgvAMSv826oeYDYOKcKfwKAFMCnyRJURlbo8utsz2I1AW2aqoyDYKkP/s1002/2022-06-25_01-16-41.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="462" data-original-width="1002" height="295" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3knPCADfno_p-rKCpemBRmEpVwA_ooKJNQR1QuJGVgkVBylSYHTpeUp_d1jemaogiGQbPAFngxVL6YL_d8ZbU1pRsrTtgmzGusJH2eCPiky5SCNSfFFvm-_M5XvBC9AD5MVgvAMSv826oeYDYOKcKfwKAFMCnyRJURlbo8utsz2I1AW2aqoyDYKkP/w640-h295/2022-06-25_01-16-41.png" width="640" /></a></div><br /><div>結果這位客服竟然同意幫我退款,只能說挺幸運,也很感謝那位客服的幫忙。寫這篇筆記時,先前訂單的付款已經退回我的 PayPal 帳戶。</div><div><br /></div><div>在此同時,我已經決定購買另一套影片剪輯軟體:<a href="https://www.movavi.com/" target="_blank">Movavi</a>(讀音是「抹-va-vi」)。</div><div><br /></div><h2 style="text-align: left;">Movavi 可以完全取代 Camtasia 嗎?</h2><div>Movavi 的影片剪輯軟體名稱是 Movavi Editor Plus,如果需要錄製螢幕畫面,則可以加購 Movavi Screen Recorder。官網上面的銷售頁面還有提供一個產品組合叫做 <a href="https://www.movavi.com/video-suite-new/" target="_blank">Movavi Video Suite</a>,裡面除了包含剛剛說的 Editor Plus 和 Screen Recorder,還有影片分割工具、影片檔案轉換工具,以及燒錄光碟的工具,價格挺划算。</div><div><br /></div><div>說到價格,如果你也想買這套軟體,我建議可以先用 Google 搜尋關鍵字 "Movavi",看看搜尋結果有沒有特惠方案。Movavi Video Suite 2022 個人版的官方定價是新台幣 1,910 元,而我用 Google 找到的特價方案是新台幣 936 元,折合美金約 31.5 元,還不到 Filmora 個人版的一半價錢。</div><div><br /></div><blockquote><div>註:我在 Google 找到的特價方案,點進去依然是進入 Movavi 官方網頁,可以確定不是詐騙或第三方轉售。比較奇妙的是,若不透過 Google 搜尋,我在 Movavi 官方網站上無論如何都找不到同樣優惠的價格。</div></blockquote><div><br /></div><div>目前我還沒有完全熟悉 Movavi 的影片剪輯功能,只做了簡單的測試。螢幕錄製的部分,基本上該有的功能都有,只是在滑鼠指標的部分,我覺得還是略遜於 Camtasia,因為 Movavi 在錄製螢幕時如果設定要包含滑鼠指標的凸顯效果,那麼錄製完成後的影片當中的滑鼠游標便都帶有凸顯效果,而且無法去除。Camtasia 在這個部分就設計得很彈性,螢幕錄製完成後,在編輯影片的時候還可以讓我們在影片中的任何區段移除或加入滑鼠指標。</div><div><br /></div><div>另一方面,Movavi 的螢幕錄製軟體可以連同按鍵操作一併錄製下來,並且在影片畫面的中間下方顯示當時的按鍵,這倒是蠻貼心的設計。以往在錄製螢幕時,如果需要一併呈現當時的鍵盤操作,我是用一個叫做 <a href="https://github.com/Code52/carnac" target="_blank">Carnac</a> 的小工具來顯示按鍵。</div><div><br /></div><div>在影片剪輯的部分,我事先調查過,Movavi 預設提供的幾個對話泡泡,基本上已經能滿足我的需求與審美觀:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU7OyocNgdmxDopJqSrBBWE4_nvOrN3AUqIJrxHvG7-sSVZ00xujkBskyrczZUO-7F8shXPbPshS0vfNsSTWP3ROdXS1zbrnKqiHhUvS2HHI9wOn-WgMiHKP_SqNakJiJJO1aiD8-P_YMfw9OIc5sq3pq0Pu0h_0iu702SZAhV3J25rVaFhusOPi4K/s788/2022-06-25_01-49-36.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="395" data-original-width="788" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU7OyocNgdmxDopJqSrBBWE4_nvOrN3AUqIJrxHvG7-sSVZ00xujkBskyrczZUO-7F8shXPbPshS0vfNsSTWP3ROdXS1zbrnKqiHhUvS2HHI9wOn-WgMiHKP_SqNakJiJJO1aiD8-P_YMfw9OIc5sq3pq0Pu0h_0iu702SZAhV3J25rVaFhusOPi4K/w640-h320/2022-06-25_01-49-36.png" width="640" /></a></div><br /><div><br /></div><div>至於其他功能,例如轉場特效、片頭動畫、常用的箭頭與圖案、子母畫面(Picturte in Picture,例如螢幕+攝影鏡頭)、馬賽克、畫面平移與縮放(Pan and Zoom)、顏色濾鏡、物件追蹤、背景聲音降躁、音量淡入與淡出……等等,看樣子已經能夠滿足我偶爾需要製作教學影片的需求。如果將來發現 Movavi 內建的圖案與動畫效果不夠用,也可以到官方網站的 <a href="https://effects-store.movavi.com/" target="_blank">Effects Store</a> 購買額外的套件。</div><div><br /></div><div>那麼,在 Camtasia 解決前面說的那個問題之前,Movavi 應該就是我用來剪片的主力了。最後列出我電腦上面已經安裝的相關工具:</div><div><ul style="text-align: left;"><li>螢幕截圖:TechSmith <a href="https://www.techsmith.com/screen-capture.html" target="_blank">Snagit</a>。</li><li>螢幕錄像:<a href="https://www.movavi.com/screen-recorder" target="_blank">Movavi Screen Recorder</a>、<a href="https://obsproject.com/" target="_blank">OBS Studio</a>(開源免費)。</li><li>影片剪輯:<a href="https://www.movavi.com/video-editor-plus/" target="_blank">Movavi Editor Plus</a>、<a href="https://www.blackmagicdesign.com/products/davinciresolve" target="_blank">DaVinchi Resolve</a>(免費版)。</li></ul><h2 style="text-align: left;">後記:初體驗 Movavi 線上技術支援(2022-6-25)</h2></div></div><div>使用 Movavi 來錄製螢幕畫面和剪輯影片時,我碰到了一個使用面的問題。當時我不確定是我自己不熟悉這套軟體,還是軟體本身設計上的瑕疵,於是我嘗試到官網打開線上支援對話窗,看看能不能得到答案。</div><div><br /></div><div>線上客服人員很親切、有耐心。首先她指引我查看一個<a href="https://img.movavi.com/online-help/srecorder/22/general_preferences.htm?st=MA%3D%3D&sct=MA%3D%3D&mw=MzAw&ms=AkQ%3D" target="_blank">線上說明的網頁</a>,裡面有我相關的選項,讓我試試看。那是 Movavi Screen Recorder 的一個選項,叫做 Capture separate streams,如下圖:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYYMzwP_Ug50b29aUk8Q_FZqsodpn-9P9JBWm00VSy2HmkwPsr6h_rhGfYpIZ2D4V2zuTmBGreZHRQ6ee2GPOLwvtJMH-0PU-bcnXjOsADoS7hOnii4ngpQZwS65VktL8lZvZ7nPCEx3g5fPbaSgYtPDycsZz0EqEhocv6uZxcDCyMfgh5viKmoxuz/s642/2022-06-25_11-12-17.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="504" data-original-width="642" height="502" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYYMzwP_Ug50b29aUk8Q_FZqsodpn-9P9JBWm00VSy2HmkwPsr6h_rhGfYpIZ2D4V2zuTmBGreZHRQ6ee2GPOLwvtJMH-0PU-bcnXjOsADoS7hOnii4ngpQZwS65VktL8lZvZ7nPCEx3g5fPbaSgYtPDycsZz0EqEhocv6uZxcDCyMfgh5viKmoxuz/w640-h502/2022-06-25_11-12-17.png" width="640" /></a></div><br /><div><br /></div><div>這個選項的作用是:當我們使用 Screen Recorder 錄製兩個影像來源時,例如同時錄下螢幕畫面和網路攝影機,只要將此選項打勾,那麼在稍後使用 Movavi Editor 來剪輯影片時,就會有兩個獨立的影音軌,讓我們能夠個別修剪或加入一些效果,例如調整攝影機畫面的位置與大小。</div><div><br /></div><div>當時我告訴客服,這個選項已經有打勾了,而且錄製出來的影音素材的確有兩個獨立的影音軌可以剪輯,但問題是螢幕畫面的這一軌,卻也已經內嵌了網路攝影機的畫面,而且無法刪除、無法調整位置與大小(因為是錄製螢幕的時候就直接結合在影片檔案了)。</div><div><br /></div><div>客服人員無法研判原因,於是接著要我做一段捕捉 log 的操作,其中包括執行一個命令列工具,以及一個 log 分析與捕捉程式,然後要我把程式捕捉下來的 log 壓縮檔案上傳給她。</div><div><br /></div><div>一切按照指示做完,關閉線上客服對話窗之後,我又繼續研究了一下。結果還真給我找到了關鍵選項:<a href="https://img.movavi.com/online-help/srecorder/22/webcam_preferences.htm?st=MA%3D%3D&sct=MA%3D%3D&mw=MzAw&ms=AkQ%3D" target="_blank">Show webcam preview during recording</a>,如下圖。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnqRx-kal-PCQWBDxhJES3GSflfB8rkDypjC0FMN37pjfpqsH2h7qpwlSoDdDQBGKud7_dIcZWpUKYAxWqpG4O66Zav4Hq8g2xbEFtUsRuuCNIhKWfivJASf8RwOJFY-M34z_hQ3Y0i1qw5DAJCG6chPEYfr5HYOYMLkDNiSpdx7iPo9OG0eD1_4ki/s642/2022-06-24_19-21-40.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="504" data-original-width="642" height="502" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnqRx-kal-PCQWBDxhJES3GSflfB8rkDypjC0FMN37pjfpqsH2h7qpwlSoDdDQBGKud7_dIcZWpUKYAxWqpG4O66Zav4Hq8g2xbEFtUsRuuCNIhKWfivJASf8RwOJFY-M34z_hQ3Y0i1qw5DAJCG6chPEYfr5HYOYMLkDNiSpdx7iPo9OG0eD1_4ki/w640-h502/2022-06-24_19-21-40.png" width="640" /></a></div><br /><div><br /></div><div>於是我再敲了線上客服,指名要找剛剛那位客服人員,好讓她知道我的問題已經解決,而先前那個 log 資料也就不用 pass 給他們的工程師了。</div><div><br /></div><div>Movavi 官網上面的說明文件也有提到這個選項的作用,但我覺得如果 Capture separate streams 選項的說明文字能夠加入底下的備註就更好了:</div><blockquote>當你勾選了這個選項,你可能會想要取消勾選 Web Camera 的 Show webcam preview during recording 選項,否則即使最終錄製的結果有兩條獨立的影音軌,螢幕畫面那軌還是會內嵌攝影機的畫面,而且無法去除。</blockquote><p><br /></p><p>總之,問題解決了,而且體驗了一次 Movavi 的線上技術支援,還知道他們的軟體有內建 log 捕捉工具,可供技術團隊查找問題,這讓我有一種安心的感覺。</p><h2 style="text-align: left;">後記:螢幕錄製的影片有嚴重殘影(2022-7-8)</h2><div>在使用 Movavi Screen Recorder 來錄製一段時間比較長的螢幕畫面時,我發現錄下來的影片檔案,某些地方會出現殘破的影像。如下圖:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU8oRQ2cgh-2HmTcdx-ZZZDAJrHpKzGkExL2kTY6ODsNHvEGMr3yf3G5-h-j1DUP4VHO-J5VTastCC0Y-be-GOAtqgbmP0J9wvjogA9_69NGHCn77bYFEsypT4zBLuSKZkHIP6m1uzfJjgYC71Z4e5s-lisxZNtujvPK0ocFJ39fAoVcPRFKCx1S2j/s1073/AfterImage-2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="969" data-original-width="1073" height="361" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU8oRQ2cgh-2HmTcdx-ZZZDAJrHpKzGkExL2kTY6ODsNHvEGMr3yf3G5-h-j1DUP4VHO-J5VTastCC0Y-be-GOAtqgbmP0J9wvjogA9_69NGHCn77bYFEsypT4zBLuSKZkHIP6m1uzfJjgYC71Z4e5s-lisxZNtujvPK0ocFJ39fAoVcPRFKCx1S2j/w400-h361/AfterImage-2.png" width="400" /></a></div><br /><div><br /></div><div>這個問題還挺嚴重的。我到官網敲了線上客服(Live Chat),把有問題的地方截圖給對方看。客服給我的建議是調整 Screen Recorder 的相關設定:</div><div><ol style="text-align: left;"><li>General > More > Use software OpenGL implementation,將此選項打勾。</li><li>如果第一個方法沒有用,再試試 Video 設定頁面中的硬體加速選項(例如我的機器會有 Intel hard acceleration),取消任何硬體加速。</li><li>如果前面的方法都無法解決問題,再到 General > More > Use alternative capture mode,將此選項打勾。</li></ol></div><div><br /></div><div>依序分別測試上述方法,發現只要把 Screen Recorder 的 Intel 硬體加速功能關閉,螢幕錄製的結果就沒有殘影。不僅如此,影片也更加流暢,不會有卡頓的情形(原先還懷疑是我把 Frame Rate 設定得太高或太低所致)。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRL_0zmnV80FALvtUt6xrxHRyIo2FmqGMb1Oz8VVcxPhEblaZ4uhXh6VQ215rItcnxyx0E3OBScOTRzV0kvQbHQrTEAVv03ra09awm0EAPgs8-HoxRHmt0NQ0BH6nikVYVO1U7PWlpX70BioDbYlmEF8HvxdadX3FdUF3hbo9axOwFlpa2RbSjagPW/s642/2022-07-09_02-33-16.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="504" data-original-width="642" height="502" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRL_0zmnV80FALvtUt6xrxHRyIo2FmqGMb1Oz8VVcxPhEblaZ4uhXh6VQ215rItcnxyx0E3OBScOTRzV0kvQbHQrTEAVv03ra09awm0EAPgs8-HoxRHmt0NQ0BH6nikVYVO1U7PWlpX70BioDbYlmEF8HvxdadX3FdUF3hbo9axOwFlpa2RbSjagPW/w640-h502/2022-07-09_02-33-16.png" width="640" /></a></div><br /><div><br /></div><div>能解決這個問題,實在太好啦!</div><div><br /></div><div>不過,我還是很懷念 Camtasia。未來只要 Camtasia 解決了 Windows Insider Program 相容性的問題,我依然會考慮購買。</div>Michael Tsaihttp://www.blogger.com/profile/00364693770445538641noreply@blogger.com0