[C#][簡報] Deferred Execution (延遲執行)
這應該算是連三次講內部 training 的 session 了,這次一樣是和部門學長合講,分享一下簡報檔。
Deferred Execution (延遲執行) from Zheng-Wei Lin
AnasAbdin
No title available
$LAYYYTER

Janaina Medeiros

roma★

#extradirty
Xuebing Du
Peter Solarz
i don't do bad sauce passes
Jules of Nature
Aqua Utopia|海の底で記憶を紡ぐ
h
YOU ARE THE REASON

izzy's playlists!

No title available
let's talk about Bridgerton tea, my ask is open

Discoholic 🪩
he wasn't even looking at me and he found me
we're not kids anymore.
Game of Thrones Daily
seen from United States
seen from United States
seen from Brazil

seen from Saudi Arabia

seen from Australia
seen from United States

seen from United States

seen from United States
seen from United States

seen from Chile

seen from United States

seen from United States
seen from United States
seen from United States
seen from United States
seen from Ukraine

seen from United States

seen from Brazil
seen from Mexico

seen from Türkiye
@xingulin
[C#][簡報] Deferred Execution (延遲執行)
這應該算是連三次講內部 training 的 session 了,這次一樣是和部門學長合講,分享一下簡報檔。
Deferred Execution (延遲執行) from Zheng-Wei Lin
[C#][簡報] Generic Delegate
一樣來分享簡報檔,這次也是和另一位學長,合講部門內 training 的一個主題。
Generic Delegate from Zheng-Wei Lin
[旅行] 單車環島 2/3 完成!
趁著端午假期,又跑去單車環 1/3 島了。這次的路線是從台北到台東,全長大概騎了 2xx 公里吧。
整理遊記和照片中,先貼一張在花蓮民宿清晨拍的照片。
[筆記] 使用 SpecFlow 搭配 Selenium 做 Web API 測試初體驗
其實是這兩天突然有的想法,於是想說來做一下驗証,順便練一下功。
雖然使用了 SpecFlow + Selenium ,但這其實不是 BDD,流程有點反過來,我先寫好標的的 API,再回過頭使用 SpecFlow 來建立整合測試的案例。
會有這想法,主要是考慮到 Web API 在執行上,相對一般的互動網頁來說,比較單純,使用 Selenium 來操作存取 API 應該也會比較簡單。至少可以不用 Selenium IDE 來錄製情境,單純的寫 code 應該就可以了。
在這次測試的經驗上來說,應該也算是証實了我的想法。整段連結 API 跟取結果的測試程式相當簡單,單純到如果要找人接手,接手的人應該也不會看不懂的程度。
只是有個需要再思考的議題是,雖然我在 Feature 檔中寫的預期結果是數字 7,但 Web API 回傳的值其實是 XML 或 JSON,所以我目前在 Assert 結果上,是直接拿 XML 碼來做比對。這部份的可讀性要怎麼改善,可能就需要再想想了。
測試的標的很簡單,我寫了一個很單純的計算機 Calculator API,裡頭單純只做數值相加的計算。程式碼如下︰
namespace TestApi.Controllers { public class CalculatorController : ApiController { [HttpGet] public int Add(int number1, int number2) { return number1 + number2; } } }
使用 SpecFlow 建立的 Feature 檔如下︰
Feature: Calculator API In order to 提供使用者數值計算能力 As a API 使用者 I want to 一個 API,可以告訴我數值計算的結果 Scenario: 兩個數值相加 Given 有以下值要計算︰ | number1 | number2 | | 3 | 4 | When 將值傳送給 API 做計算 Then API 回傳值為 7
展開後修改的 Step 檔如下︰
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenQA.Selenium; using OpenQA.Selenium.Firefox; using TechTalk.SpecFlow; namespace TestApi.Spec { [Binding] public class CalculatorAPISteps { private IWebDriver driver; [BeforeScenario()] public void Setup() { driver = new FirefoxDriver(); } [AfterScenario()] public void TearDown() { driver.Quit(); } [Given(@"有以下值要計算︰")] public void Given有以下值要計算(Table table) { ScenarioContext.Current.Add("Number1", Convert.ToInt32(table.Rows[0]["number1"])); ScenarioContext.Current.Add("Number2", Convert.ToInt32(table.Rows[0]["number2"])); } [When(@"將值傳送給 API 做計算")] public void When將值傳送給API做計算() { int number1 = ScenarioContext.Current.Get<int>("Number1"); int number2 = ScenarioContext.Current.Get<int>("Number2"); driver.Navigate().GoToUrl("http://localhost:7070/Api/Calculator/Add?number1=" + number1 + "&number2=" + number2); string response = driver.PageSource; ScenarioContext.Current.Add("Response", response); } [Then(@"API 回傳值為 (.*)")] public void ThenAPI回傳值為(int result) { string actual = ScenarioContext.Current.Get<string>("Response"); string expected ="<int xmlns=\"http://schemas.microsoft.com/2003/10/Serialization/\">" + result + "</int>"; Assert.AreEqual(expected, actual); } } }
測試程式碼可在這裡下載︰TestApi.zip
簡單筆記一下 :)
Behavioural testing in .Net with SpecFlow and Selenium (Part 1)
Behavioural testing in .Net with SpecFlow and Selenium (Part 2)
[C#][簡報] Delegate Introduction
貼上上週和學長合講的內部 training 課程的簡報檔內容。
Delegate (委派) Introduction from Zheng-Wei Lin
[C#][筆記] Delegate 備課漢參考來源整理
最近和部門學長要合講一個部門 training 的 session,主題是 C# 中的 Delegate (委派)。因為議題還牽扯滿多部份的 (事件、匿名方法、Closure 等等),所以看了不少的參考資料,想說整理成篇方便自己未來查詢吧。
主要參考來源,分為 MSDN、書籍與論壇、Blog 文章,分列如下︰
MSDN 參考資料
delegate (C# 參考)
event (C# 參考)
委派 (C# 程式設計手冊)
事件 (C# 程式設計手冊)
匿名函式 (C# 程式設計手冊)
ASP.NET Page Life Cycle Overview
書籍參考資料
Learning C# 3.0: Master the fundamentals of C# 3.0 - Delegates and Events
C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers - Delegates, Events, and Lambda Expressions
C# 4.0 in a Nutshell - Chapter 04 Advanced C#
C# in Depth - Chapter 05 Fast-tracked delegates、Chapter 09 Lambda expressions and expression trees
論壇、Blog 文章
Huan-Lin 學習筆記 - C# 筆記:重訪委派-從 C# 1.0 到 2.0 到 3.0
Huan-Lin 學習筆記 - C# 筆記:從 Lambda 表示式到 LINQ
In 91 - [.NET]快快樂樂學LINQ系列前哨戰-Lambda的簡介
蔡學鏞【言程序】部落格 - 思考函數編程(三)FP is as FP does
老赵点滴 - 追求编程之美 - 从.NET中委托写法的演变谈开去(上):委托与匿名方法
老赵点滴 - 追求编程之美 - 从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势
老赵点滴 - 追求编程之美 - 从.NET中委托写法的演变谈开去(下):性能相关
老赵点滴 - 追求编程之美 - 警惕匿名方法造成的变量共享
老赵点滴 - 追求编程之美 - 您善于使用匿名函数吗?
InfoQ 中文 - 高阶函数、委托与匿名方法
Stack Overflow 討論 delegate 是否為 reference type 的討論串 - Why are delegates reference types?
如果再來還有閱讀什麼新的參考文章,也會再更新上這篇文章。
[Tip] 改善 Google Docs 在 Windows 環境下的中文顯示字型
這也是困擾我一陣子的小問題。
之前我在考慮使用 Google Docs 來做分享,英文簡報其實沒什麼問題,但中文簡報,就會醜醜的。原因是 Google Docs 會取用新細明體來做為中文顯示字型,如下︰
解決方式也很簡單,在 Chrome 下我們可以先安裝 Stylish 套件,再針對 docs.google.com 網址,加入以下的樣式設定︰
* { font-family: "微軟正黑體", sans-serif !important; }
這樣就可以改變 Google Docs 的中文顯示字型了,在顯示中文簡報上,也會更美觀。
調整後效果如下︰
是不是覺得比較好看了呢?最後附上 Stylish 的設定抓圖如下,供參考︰
[Tip] 使 Windows 下的熱鍵 (Hotkey) 行為與 Mac OS X 達成一致 (使用 AutoHotkey)
標題很奇怪,其實是我一直以來的小小困擾 XD
我自己是 Mac/Windows 兩個 OS 平台的使用者與開發者,平常會視狀況決定開哪個 NB 來做事。
在兩個平台切換其實是習慣問題,但我到現在仍然不習慣的,就是兩個平台在鍵盤上對應的不同,讓我在切換平台工作時,常會有按錯鍵的問題。比方說,Mac 下的 「Command + 左鍵」 = Windows 的 「Home 鍵」這件事,就常會讓我在兩平台寫 code 時按錯鍵,讓我分心。
我常會在 Windows 下一直按 Alt + 左鍵或是 Ctrl + 左鍵,然後期待編輯器的游標切換,結果都會失敗......。類似的還有 Command + 右鍵、Command + 上鍵、Command + 下鍵等 hot key。
今天總算克服了懶病,在 Windows 下使用 AutoHotkey,加上了以下對應,讓 Windows 下的 Alt + * 和 Mac 下的 Command + * 熱鍵可以對齊,來解決這個困擾。
設定內容如下,只要加到自己的 AutoHotkey 設定檔的最下面,再重新載入設定,就可以享用此熱鍵設定了。
!Left::Send {Home} ; Alt + 左鍵 = Home !Right::Send {End} ; Alt + 右鍵 = End !Up::Send {PgUp} ; Alt + 上鍵 = Page Up !Down::Send {PgDn} ; Alt + 下鍵 = Page Down
如果有不清楚 AutoHotkey 設定的朋友,可以閱讀以下這篇介紹文︰
Turn Any Action Into a Keyboard Shortcut: A Beginner's Guide to AutoHotkey
[Tools] 操作熱鍵與熱字串的超便利工具:AutoHotKey
當然還有其他熱鍵,應該也可以用這種方式進行設定。有興趣的朋友們可以再進行擴充,謝謝!
[C#][筆記] foreach、Iterator 與 yield
一、foreach 與 IEnumerable、IEnumerable<T> 的關係
在上篇文章中,和大家說明了 IEnumerable、IEnumerator 與它們的泛型版本 IEnumerable<T>、IEnumerator<T> 的原理與作用後,本篇將再進一步說明,IEnumerable 與 IEnumerator 的用途。
大家在看了列舉後,可能會發現,這種「一個個將資料集合中的元素取出來」的行為,和 C# 語言中的 foreach 行為很接近?假設我們有個整數串列如下︰
List<int> myList = new List<int>() { 1, 2, 3 };
典型的 foreach 用例如下︰
foreach (int item in myList) { Console.WriteLine(item); }
相同的範例,使用列舉器 (Enumerator) 的方式如下︰
IEnumerator<int> enumerator = myList.GetEnumerator(); while (enumerator.MoveNext()) { int item = enumerator.Current; Console.WriteLine(item); }
大家可以比較看看,基本上兩者的結構非常接近,只是使用列舉器的版本,在使用上比較繁瑣一點。
而實際上,foreach 關鍵字其實是 C# 提供的語法糖衣 (Syntactic sugar),foreach 的程式在被編譯後,轉成中間程式碼 (IL) 時,其實編譯器會將 foreach 的程式,轉為列舉器 (Enumerator) 的版本。
為了証明這點,我們可以使用 IL DASM,將上述兩段程式,在編譯後,分別觀看其 IL 程式碼。
foreach 的程式,其 IL 如下︰
列舉器的程式,其 IL 如下︰
可以看到 foreach 版的 IL 除了多了一些 try cache 的程式片段外,基本結構與列舉器的 IL 程式是相同的。foreach 版本的 IL,會將 in 關鍵字後的變數,轉化為取此變數的列舉器 (GetEnumerator() 方法),並在後續程式中,使用此列舉器取出資料
也因為 foreach 會將 in 後的變數,轉化為使用變數的 GetEnumerator() 方法來取出列舉器,因此編譯器會要求,in 關鍵字後的變數,必需要實作 Getenumerator() 方法。
我們可以試著在 in 關鍵字後,傳入沒有實作 GenEnumerator() 的型別變數︰
int foo = 3; foreach (int item in foo) { Console.WriteLine(); }
將上述程式進行編譯,編譯器會報以下的錯誤訊息︰
foreach statement cannot operate on variables of type 'int' because 'int' does not contain a public definition for 'GetEnumerator'
表示 foreach 無法使用沒有實作 GetEnumerator() 方法來做列舉的動作。
而這也是為什麼 MSDN 文件上,會說明希望可以被 foreach 操作的型別,都要實作 IEnumerable 或是 IEnumerable<T> 界面的原因,雖然我們不實作界面,僅實作 GetEnumerator() 方法,也可以給 foreach 使用。
但我覺得,實作了 IEnumerable 或 IEnumerable<T> 界面,至少在語意上,會讓使用此型別的人了解,此型別是「可列舉的」、「可被用於 foreach 中」的,而不必再去了解該型別下的方法列表,有沒有 GetEnumerator() 方法的實作,會比較簡單一點,也會提高使用上的一致性。
因此,針對上述文章的主程式,我們其實可以改為使用以下的 foreach 用法︰
MyWeekdayList weekdayList = new MyWeekdayList(); //// 逐一印出每日的名稱 foreach (var day in weekdayList) { Console.WriteLine(day); }
二、Iterator 與 yield
由上篇的說明,我們可以看到,要實作一個資料結構型別,並使它具備可被列舉的能力,所需要的步驟實在太繁瑣了,整理步驟如下︰
此型別需實作 IEnumerable 或 IEnumerable<T> 型別,並實作其 GetEnumerator() 方法
GetEnumerator() 內容中,需回傳實作 IEnumerator 或 IEnumerator<T> 界面的列舉器型別物件
最後再實作此列舉器型別,加入其中的 MoveNext()、Reset() 方法與 Current 屬性
因為步驟實在太複雜,所以 C# 提供了更為簡便的方式,也就是使用 Iterator 的做法。
Iterator 是一種 Design Pattern,我摘列「大話設計模式」一書中,對此模式的說明︰
Iterator 提供一種方法依序存取一個聚合物件中各個元素,而又不暴露該物件的內部表示。
而在 MSDN 文件上,針對 Iterator 的說明,摘列其中幾項重點說明如下︰
Iterator 是程式碼區段,會傳回相同型別之按順序排列的值。
在為類別或結構 (Struct) 建立 Iterator 時,您並不需要實作整個 IEnumerator 介面。 編譯器會在偵測到您的 Iterator 時,自動產生 IEnumerator 或 IEnumerator<T> 介面的 Current、MoveNext 和 Dispose 方法。
yield 傳回陳述式 (Statement) 會使來源序列 (Sequence) 中的項目,在即將存取來源序列中的下一個項目時傳回到呼叫端
yield 關鍵字會用來指定所傳回的單一個或多個值。 當到達 yield return 陳述式時,便會儲存目前的位置。 下一次呼叫此 Iterator 時,便會從這個位置重新開始執行。
是不是感覺,yield 關鍵字的作用,兼具了之前我們討論列舉器時,所談到的指標與 Current 屬性、MoveNext() 方法呢?
事實上,yield 關鍵字,在 C# 中也是另一個語法糖衣,Compiler 在編譯時,編譯器其實也會在編譯時,將它轉為,有實作 IEnumerable<T> 等界面的類別,這部份可以看 Dot Net Perls 的 Yield Tutorial 文章。
我們將 MyWeekdayList 中,相關 Iterator 的程式碼,在編譯後,以 dotPeek 對執行檔做 Decompile 的動作,看看編譯器在後面為我們做了什麼事?
節錄抓圖如下︰
完整 Decompile 後的程式,我也放置在 Gist 上︰https://gist.github.com/LittleLin/5463721
但事實上 yield 除了可以協助產生 Iterator 之外,它也具備有所謂「延遲執行」的功能,這部份有興趣的朋友,可以閱讀老趙的Why Java Sucks and C# Rocks(6):yield及其作用一文。
References
Book - C# 4.0 in Nutshell
Book - 大話設計模式
[.NET]快快樂樂學LINQ系列前哨戰-IEnumerable, IEnumerator, yield, Iterator
MSDN - Iterator
Dot Net Perls - Yield
Why Java Sucks and C# Rocks(6):yield及其作用
[C#][筆記] IEnumerable、IEnumerator 與 IEnumerable<T>、IEnumerator<T>
一、前言
在寫程式上,我們常會使用到許多資料結構,如串列 (List)、HashTable (C# 下的 Dicationay)、堆疊 (Stack)、樹 (Tree),僅管這些資料結構在內部實作與外部使用的界面上,各有不同,但基本上我們都會希望這些資料結構,能提供有巡訪 (Traverse) 資料的能力。
所謂的巡訪,就是逐一走過資料結構下,內含的所有的資料元素 (Element) 或節點 (Node),當然在不同的資料結構上,依照內部實作不同,巡訪的演算法也會各有不同。
舉例來說,串列的巡訪,基本上就是從串列的第一個節點資料,走到最後一個節點去,比較單純。而以樹狀結構來說,就有不少巡訪方式的變形,比如說前序巡訪 (Preorder),後序巡訪 (Postorder) 等, 如下圖所示︰
.Net 透過 IEnumerable、IEnumerator 這兩個界面 (interface),與它們的泛型版本︰IEnumerable<T>、IEnumerator<T>,來提供資料結構這樣的巡訪能力。事實上整個 .Net 集合 (Collection) 的界面,處在上層的就是 IEnumerable、IEnumerable<T> 與 IEnumerator、IEnumerator<T> 4 個界面。
我們可以看看下面這張,從 C# 4.0 in Nutshell 書中節錄出來的整理圖,再回頭比對 MSDN 文件︰
由圖中我們可以理解,在 .Net 集合中常用的界面 (ICollection、IList、IDictionary),都有繼承 IEnumerable 或是 IEnumerable<T>,也因此實作這些界面的集合,也才會都具備資料巡訪的能力。
二、IEnumerable、IEnumerator 與 IEnumerable<T>、IEnumerator<T>
這時候我們可以來看看 IEnumerable、IEnumerator、IEnumerable<T>、IEnumerator<T> 的作用了。
2.1 IEnumerable、IEnumerable<T>
首先我們先檢視 IEnumerable 與 IEnumerable<T> 界面,中文語義上,我們可以理解 IEnumerable 為「資料結構中的資料,是否可被列舉」。
在 IEnumerable 界面的內容中,可以看到只有一個 GetEnumerator() 方法︰
public interface IEnumerable { IEnumerator GetEnumerator(); }
可以看到 .Net 對「是否可被列舉」這件事的定義上,就是認定是否具備取得「列舉器」(Enumerator) 的能力。
2.2 IEnumerator、IEnumerator<T> 列舉器
而什麼是列舉器呢?我們可以將 Enumerator 視為是一個巡覽器,負責將它所附屬的資料集合中的元素,一個一個的取出並回傳。
.Net Framework 將巡覽器的定義置於 IEnumerator 界面,內含以下兩個方法與一個屬性︰
public interface IEnumerator { bool MoveNext(); object Current { get; } void Reset(); }
這兩個方法與一個屬性的作用,我們可以參照 C# Cornor 中的一張整理圖如下︰
我們可以將列舉值的內部,視為有一個指標,指向其附屬的資料集合的元素。指標一開始的位置,是在第一個元素之前,也就是不指向任何資料元素。
而 Current 屬性,即是回傳目前指標指向的值的內容;MoveNext() 則是將指標往下移一個位置,並回傳 true/false 來告知,指標向下移動是否成功?(下一個位置已無資料,則表示失敗);而 Reset() 方法,則是再次將指標移動到資料集合中,第一個元素之前的位置。
綜合上述,我們可以整理的關係如下︰
1. 要讓自定義的資料型別,具備「可被列舉」的能力,需要實作 IEnumerable 或是 IEnumerable<T> 界面
2. 在 IEnumerable 與 IEnumerable<T> 界面中,定義了 GetEnumerator() 方法,回傳列舉器 (IEnumerator) 物件
3. 列舉器是真正實作,巡訪各個資料元素的主體物件
三、程式範例
講了這麼多,總算可以開始以程式碼做示範了。在這邊我先簡單實作一個簡單的 Weekday List︰MyWeekdayList,內部的資料結構,含有星期一到星期日的縮寫。
/// <summary> /// 自定義的 Weekday 串列 /// </summary> public class MyWeekdayList : IEnumerable { /// <summary> /// 內建的 Weekday 列表 /// </summary> private string[] _weekdays = new string[] { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; /// <summary> /// 實作 IEnumerable<T>.GetEnumerator 方法 /// </summary> /// <returns>T 型別列舉器</returns> public IEnumerator GetEnumerator() { return new WeekdayEnumerator(this); } //// ... 省略 }
在程式中可以看到,為了讓它具備可列舉的能力,因此我們實作了 IEnumerable<T> 界面,並實作了 GetEnumerator() 方法,方法的內容也很簡單,僅是單純回傳一個 WeekdayEnumerator 列舉器的物件。WeekdayEnumerator 列舉器的實作如下︰
/// <summary> /// 針對 MyWeekdayList 資料結構實作的列舉器 /// </summary> class WeekdayEnumerator : IEnumerator<string> { /// <summary> /// 待被巡訪的資料元素列表 /// </summary> private string[] _elements; /// <summary> /// 目前列舉器的資料指標 /// </summary> private int _flag = -1; /// <summary> /// Initializes a new instance of the <see cref="WeekdayEnumerator" /> class /// </summary> /// <param name="list">被巡訪的 MyWeekdayList 物件</param> public WeekdayEnumerator(MyWeekdayList list) { this._elements = list._weekdays; } /// <summary> /// 實作 IEnumerator<T>.Reset() /// </summary> public void Reset() { this._flag = -1; } /// <summary> /// 實作 IEnumerator<T>.Current /// </summary> public string Current { get { if (this._flag == -1 || this._flag > this._elements.Length) { throw new InvalidOperationException(); } return this._elements[this._flag]; } } /// <summary> /// 實作 IEnumerator<T>.MoveNext() /// </summary> /// <returns>指標下移是否成功</returns> public bool MoveNext() { if (this._flag <= this._elements.Length) { return false; } else if (this._flag + 1 <= this._elements.Length) { return false; } else { this._flag++; return true; } } //// ... 省略 }
可以看到我們在列舉器中,就是將 Weekday List 中的元素陣列,一個個取出並回傳給使用端。而使用端的用法如下︰
//// 取出列舉器 MyWeekdayList weekdayList = new MyWeekdayList(); IEnumerator enumerator = weekdayList.GetEnumerator(); //// 逐一印出每日的名稱 while (enumerator.MoveNext()) { Console.WriteLine(enumerator.Current); }
完整程式一樣放置在 Gist 上︰https://gist.github.com/LittleLin/5457385
補充
對樹狀結構的巡訪有興趣的朋友,可以再閱讀以下的兩篇參考連結︰
演算法筆記 - Tree
[MMDays專欄] 由樹的前序、中序、後序走法來談資料結構
References
Book - C sharp 4.0 in Nutshell
Dot Net Perls - C# IEnumerable
C# Corner - Enumerator Tutorial
[.NET]快快樂樂學LINQ系列前哨戰-IEnumerable, IEnumerator, yield, Iterator
MSDN - IEnumerable 介面
MSDN - IEnumerable<T> 介面
MSDN - IEnumerator
MSDN - IEnumerator<T>
[C#] 使用 IEnumerable<T> 與 yield 機制,實作樹狀結構巡訪 (Tree Traversal)
其實我原本是想要測試 yield 可不可以在適用在遞迴的情形下,順手就做了一版簡單的 Preorder Tree Traversal。
我們先定義樹的節點 class,其中我們加上 Preorder 屬性,會回傳針對樹狀資料結構,做 preorder 巡訪的結果︰
/// /// 樹狀結構節點 /// /// 節點資料型別 public class Node { /// /// Initializes a new instance of the class. /// /// 節點資料 public Node(T data) { this.Data = data; } /// /// 節點資料 /// public T Data { get; set; } /// /// 左子樹 /// public Node Left { get; set; } /// /// 右子樹 /// public Node Right { get; set; } /// /// Preorder Traversal /// public IEnumerable Preorder { get { return this.ScanPreorder(this); } } /// /// Preorder Traversal Algorithm /// /// 巡訪的樹 /// 巡訪結果 private IEnumerable ScanPreorder(Node tree) { // 節點不得為 null if (tree == null) { throw new ArgumentNullException(); } // preorder Traversal yield return tree.Data; if (tree.Left != null) { foreach (var node in this.ScanPreorder(tree.Left)) { yield return node; } } if (tree.Right != null) { foreach (var node in this.ScanPreorder(tree.Right)) { yield return node; } } } }
我們要巡訪的樹,長相如下︰
使用方式如下︰
// 建立樹狀結構 Node tree = new Node(0); tree.Left = new Node(1); tree.Right = new Node(2); tree.Left.Left = new Node(3); tree.Left.Right = new Node(4); tree.Right.Right = new Node(5); tree.Left.Left.Left = new Node(6); tree.Left.Left.Right = new Node(7); tree.Right.Right.Left = new Node(8); tree.Right.Right.Right = new Node(9); // Preorder,印出上圖中,Preorder 的順序 Console.Write("Preorder:\t"); foreach (var node in tree.Preorder) { Console.Write(node); }
因為這篇算倉促為之,後續再補上原理和完整程式 XD
[羅漢咖的灶咖] 秀珍菇炒飯
因為最近搬到一間有廚房的租處 (和其他房客共用),所以總算可以開始學習自己做菜給自己吃,做一位稱職的羅漢咖了!
於是再來也會開始在 blog 上,放一下做菜成果的照片,順便記錄一下步驟。當然身為初學者,做菜難吃是很正常的,也算是幫自己記錄一下歷程。
今晚做的菜是「秀珍菇炒飯」,故名思義就是秀珍菇 + 炒飯啦,沒什麼學問,所以簡單來記錄一下材料和成果︰
材料
白飯一碗︰份量依自己的食量做調整
蛋兩顆
秀珍菇 1/3 盒︰秀珍茹我是到全聯去買的,一盒 $32
葱一小把
成果照如下︰
[C#][筆記] Value Type (實值型別) vs. Reference Type (參考型別)
在 MSDN 上,將 .Net 下所有的類別,分為以下三類︰
Value Type (實值型別)︰常見的型別是 struct、int、char、double 等
Reference Type (參考型別)︰典型的例子就是使用 class 關鍵字定義的型別
Pointer Type (指標型別)
其中 Pointer Type,主要是像 C/C++ 語言,可以用來對記憶體直接操作。但因為使用到它的機會比較特別,我自己使用到它的機會也沒有太多,所以本文暫時先不針對此型別做討論。
而 Value Type 與 Reference Type 兩者在 .Net 下的型別階層體系的差別上,最明顯的差別與區別方式在於,Value Type 皆繼承自 System.ValueType,因此如果不是繼承自 System.ValueType 的型別,都不是 Value Type。
針對 Value Type 與 Reference Type 的差異,我們可以先以以下兩句 ,簡單做個說明︰
在 Value Type 變數中,儲存的值是「實值」(Value),像是整數、浮點數、布林、字元等
在 Reference Type 變數中,儲存的值是「參考」(Reference),也就是記憶體的位址 ,指向儲存真實內容的記憶體區塊的開始位置。
也就是說,Value Type 與 Reference Type 最顯著的差別,是他們在記憶體中,儲存其變數值的方式。再往下繼續探討前,我覺得我們可以先建立以下的基礎觀念︰
C# (或說大部份的程式語言) 會將記憶體分為兩大用途︰Stack 與 Heap。
C# 中所有的區域變數 (不管是 Value Type 或是 Reference Type),其內容 (變數名稱、型別與與值) 都是儲存在 Stack 中。Value Type 變數儲存的內容是「實值」,Reference Type 變數儲存的內容是「參考」。
使用 new 關鍵字實體化類別的物件,其物件內容,是儲存在 Heap 中。Reference Type 變數中所儲存的參考,其實是指向 Heap 中的記憶體位址。
墳墓大大之前有寫了一系列關於 C/Java 在記憶體管理上,非常好的系列文章,大家可以閱讀第一、二篇 (其實全系列 8 篇都很推薦閱讀),以建立上述觀念︰
淺談 C 語言和 Java 的記憶體管理(一)
淺談 C 語言和 Java 的記憶體管理(二)
為了測試兩者在記憶體儲存上的差別,我建立測試用 Reference Type 型別與 Value Type 型別如下︰
/// /// 測試用 Reference Type 型別 /// public class DemoReferenceType { int _field; public int Field { get { return this._field; } set { this._field = value; } } public DemoReferenceType(int val) { this._field = val; } public override string ToString() { return "Field=" + this._field.ToString(); } }
另外建立測試用 Value Type 如下︰
/// 測試用 Value Type 型別 /// public struct DemoValueType { int _field; public int Field { get { return this._field; } set { this._field = value; } } public DemoValueType(int val) { this._field = val; } public override string ToString() { return "Field=" + this._field.ToString(); } }
我們以下列程式進行測試︰
// 建立 Reference Type 與 Value Type 的測試物件 DemoReferenceType refVar1 = new DemoReferenceType(1); DemoValueType valVar1 = new DemoValueType(1); // 將變數 1 的值,指派給變數 2 DemoReferenceType refVar2 = refVar1; DemoValueType valVar2 = valVar1; // 變更變數 2 的欄位值 refVar1.Field = 2; valVar2.Field = 2; // 印出︰ // refVar1 Field=2 // refVar2 Field=2 // valVar1 Field=1 // valVar2 Field=2 Console.WriteLine("refVar1 " + refVar1); Console.WriteLine("refVar2 " + refVar2); Console.WriteLine("valVar1 " + valVar1); Console.WriteLine("valVar2 " + valVar2);
可以發現,在 Reference Type 與 Value Type 下,我們都是將變數一 (refVar1、valVar1) 的值指派給變數二 (refVar2、valVar2),並修改變數二的欄位值。但最後的結果卻不相同。
直覺上我們會變得 Reference Type 的情形不符直覺,明明我已經變數一(refVar1) 「指派」(assign) 給變數二 (refVar2) 了,為什麼我修改變數二的欄位值,看起來好像也影響到變數一的欄位值了呢?
這是因為兩者間所交換儲存的值不同,在「指派」這件事上,其實兩種型別做的事都是相同的,即「將變數一儲存的值,建立一個複本並儲存到變數二中」,只是 Value Type 建立的複本是資料本身,而 Reference Type 的複本是參考。
我們以圖例來進行說明,下圖是我們建立變數一時,記憶體中的配置方式︰
可以看到,所有的區域變數,都是儲存於 Stack 中,Reference Type 中儲存的是參考,內含是 Heap 中的記憶體位址。
而在建立變數二,並將變數一 (refVar1、valVar1) 的值指派給變數二 (refVar2、valVar2)後,記憶體中的配置如下︰
如同先前所說,「指派」這件事,不管在 Reference Type 或是 Value Type 都是一樣的,都是將變數一的儲存的值,複製一份到變數二。
而因為 Reference Type 中儲存的是參考,因此變數二 (refVar2) 會儲存與變數一 (refVar2) 相同的記憶體位址。也就是其實 refVar2 與 refVar1 指向的是在 Heap 上,相同的物件內容。
最後我們可以看看,同樣針對變數二的欄位做變更,在記憶體上的儲存情形︰
在此我們可以看到,由於 Reference Type 中的 refVar1 與 refVar2 中儲存的參考值相同,也就是兩者背後指向的是相同的物件內容。因此使用 refVar2 變數修改 Field 欄位內容,再取出 refVar1.Field 欄位值,兩者數值是相等的也就是完全合理的了。
最後整理一下重點如下︰
在 C# 中,記憶體用途分為 Stack 與 Heap 兩種,所有的區域變數 (不管是 Value Type 或是 Reference Type) 都儲存於 Stack 下,使用 new 關鍵字實體化的類別實體,則儲存於 Heap 中
Value Type 儲存的是內容 (實值),Reference 儲存的是位址 (參考)
由於 Value Type 與 Reference Type 在記憶體儲存值上的差異,在使用上若不理解,有時會造成意料外的問題
References
淺談 C 語言和 Java 的記憶體管理(一)
淺談 C 語言和 Java 的記憶體管理(二)
[讀書心得]Runtime Type Fundamentals from CLR via C#
Book - 進入 IT 產業必讀的 200 個 .NET 面試決勝題
MSDN - Types (C# Reference)
MSDN - Reference Types (C# Reference)
MSDN - Value Types (C# Reference)
Six important .NET concepts: Stack, heap, value types, reference types, boxing, and unboxing
[C#][筆記] 關於列舉 (enum) 型別特性的整理
本篇算是整理,針對列舉 (enum) 型別的一些使用上的心得。
首先要先點出的是,列舉是 Value Type,不是 Reference Type,因此在列舉型別物件中,儲存的值不會是 null,所以如果錯誤使用它的話,有時候會產生意想不到的結果。
第二點是,從前一篇 [C#][筆記] default 關鍵字針對不同型別的回傳值整理 文章的實驗結果中,我們可以看到,針對列舉型別,使用 default 關鍵字,回傳的值是 0。
我們舉下面的 Sample Code 做為範例 (這是真實案例,我從別人的 code 中抽出來的),程式中我們定義了一個列舉 TestEnum,有 Value1、Value2、Value3 三個成員。
/// /// 測試用列舉 /// public enum TestEnum { Value1, Value2, Value3 }
在主程式中,我們有一個 List 變數,名稱為 testList,內含有列舉中的 Value1 與 Value2 成員。程式的需求,是判斷這個 List 變數中,有沒有包含 Value3 這個列舉值成員。
因為有人把列舉視為 Reference Type,因此就寫出了以下的程式碼︰
bool isListContainValue3 = false; List<TestEnum> testList = new List<TestEnum>() { TestEnum.Value1, TestEnum.Value2 }; isListContainValue3 = testList.FirstOrDefault(element => element == TestEnum.Value3) != null; // 總是印出 True Console.WriteLine(isListContainValue3);
我們先不管誤用 FirstOrDefault() 這個 method 來做「判斷一個 List 是否包含某個值」的問題,上述的程式,如果我們真正執行的話,我們會發現,程式永遠會印出 True,即使上述 testList 並不包含 Value3 這個列舉成員。
會出現這問題,是因為錯誤將列舉視為 Reference Type,認為如果在清單中查不到成員的話,default(TestEnum) 會回傳 null 值。
但我們已經知道,列舉其實是 Value Type,因此列舉變數是不能儲存 null 值的。在此例中,testList.FirstOrDefault() 這個 method 會回傳的是 default(TestEnum),也就是 TestEnum.Value1 這個列舉成員。也因此結果永遠不會是 null,印出來的值則永遠印出的是 True。
算是以一個例子,來說明誤解列舉型別,在某些情形下,是真的會造成問題的。
最後,以上述的需求來看,其實可以直接使用 List 類別的 Contains method,就可以了︰
isListContainValue3 = testList.Contains(TestEnum.Value3);
References:
MSDN: List.Contains Method
Stackoverflow 上的討論︰Using List.Find or LINQ on lists of enums in .NET 3.5
[C#][筆記] default 關鍵字針對不同型別的回傳值整理
default 是在 C# 中,用以取得指定型別 (type) 的「預設值」的關鍵字,簡單的範例用法如下︰
default(型別);
從 MSDN 上關於 default keyword 的說明頁面中,整理「預設值」的回傳規則如下︰
如果指定的型別是 Reference type 的話,則 default 關鍵字回傳值是 null
如果指定的型別是數值類型 (numeric) Value type 的話,則 default 關鍵字回傳值是 0
如果指定的型別是可為 null (nullable) Value types 的話,則 default 關鍵字回傳值是 System.Nullable<T>
如果指定的型別是 struct 的話,則 default 關鍵字回傳值是一個新的 struct object,但 struct object 中的每個成員的值,內容一樣會遵照上述三個規定做設定
但上述的描述中,我們看到,MSDN 中沒有針對像 DateTime、列舉等型別的預設值做出說明,所以我撰寫了簡單的測試程式,來做這部份預設值的實驗。
為了測試方便,所以我定義了一個類別、一個結構與兩個列舉型別 (一個有針對成員設定值,另一個則沒有),以方便測試,型別定義整理如下︰
類別︰DemoClass
/// /// 測試用類別 /// public class DemoClass { /// /// Value Type 屬性 /// public int ValueTypeProperty { get; set; } /// /// Reference Type 屬性 /// public List ReferenceTypeProperty { get; set; } }
結構︰DemoStruct
/// /// 測試用結構 /// public struct DemoStruct { /// /// Value Type 屬性 /// public int ValueTypeProperty { get; set; } /// /// Reference Type 屬性 /// public List ReferenceTypeProperty { get; set; } }
列舉︰DemoEnum (指定每個列舉值,背後代表一個整數)
/// /// 測試用列舉 (指定每個列舉值,背後代表一個整數) /// public enum DemoEnum : int { Value1 = 1, Value2 = 2 }
列舉︰DemoEnumWithoutValue (不指定每個列舉值,背後代表一個整數)
/// /// 測試用列舉 (不指定每個列舉值,背後代表一個整數) /// public enum DemoEnumWithoutValue : int { Value1, Value2 }
針對各型別的 default keyword 回傳值,我整理如下表︰
型別 default 回傳值 說明 List null Reference Type 回傳 null string null int 0 數值型的 Value Type 回傳 0 int? null 回傳 System.Nullable<int> DateTime 0001/1/1 上午 12:00:00 即為 DateTime.MinValue DateTime? null 回傳 System.Nullable<DateTime> DamoClass null DamoStruct ValueTypeProperty = 0 ReferenceTypeProperty = null 結構的每個成員,其值設定為 default 關鍵字回傳的預設值 DemoEnum 0 列舉成員值有設定數值,則回傳 0 DemoEnumWithoutValue Value1 列舉成員值沒有設定數值,則回傳此列舉的第一個成員 (其值為 0)
補充說明︰
1. 針對 DateTime 型別,首先要先注意的是,DateTime 型別是 struct,所以是 Value Type 而不是 Reference type。
而 DateTime 內部的結構,其實是記錄一個 ulong 型別的 ticks (千萬分之一秒) 屬性值。當我們對 DateTime 型別做操作或是取得日期資訊 (ex: 年、月) 時,背後其實是使用 ticks 屬性值來做相關的計算。
因此所謂的 DateTime.MinValue,其實就是 ticks 屬性為 0 的 DateTime object,而 .Net Framework 定義 ticks 屬性值為 0 的起始點為︰0001 年 1 月 1 日午夜 12:00:00。
而 DateTime 型別因為是 struct,因此 default(DateTime) 會回傳一個 DateTime object,其中的 ticks 屬性因為是 long 型別,所以預設值為 0。因此 default(DateTime) 的值會與 DateTime.MinValue 值相等 (因此兩者內部的 ticks 皆為 0)。
2. 另外在這邊要注意,在列舉型別中,如果沒有特別針對成員設定值的話 (像 DemoEnum),其成員的值是從 0 開始編號。因此 DemoEnumWithoutValue.Value1,背後的值其實是 0,所以 default(DemoEnumWithoutValue) 會顯示 Value1,其實是合理且一致的。
測試程式︰
測試程式我已經上傳到 GitHub,網址如下︰https://gist.github.com/LittleLin/5390639,可以使用 SnippetCompiler 來運行上面的測試程式碼,或是開 Visual Studio 執行也可以。
References:
MSDN: DateTime Structure 說明頁
MSDN: enum 說明頁
MSDN: default keyword 說明頁
騎 ubike 下班滿月感言
騎 ubike 下班已經進入第五週了 (當然是在沒下雨的狀態)。 話說從頭,當初原本是想買一台新的單車,可以環島上班兩相宜的。預算都撥了,候選名單也列了幾台車,原本打算去實體店面試騎看看,比價價格不要差太多就可以下手了,有機會還可以幫公司衝一下業績。 然後就看到了 ubike 的方案,突然想說,比起真的買一台單車,或許租車會是更經濟的選擇,可以省去保養車子,停車跟擔心車子被偷等要關心的事情,對我這個懶人來說似乎不錯。 另外從費用來看,ubike 目前租車的費率,一小時只要十元,(前 30 分鐘免費,再來每 30 分鐘是十元),而我平常上班搭捷運,一趟要 20 元。所以不考慮花費時間的話,騎 ubike 比起搭捷運,也要來得划算得多。 總之綜合上述,就決定暫時擱置買單車的計劃,開始騎 ubike 下班的日子了。目前歷時四週,覺得非常不錯,下班時騎 ubike 回家,腦袋還行就邊騎邊整理思路,不行時就一路放空,整體來說其實還滿不錯的。希望可以繼續保持下去啦!
MongoDB 全文搜尋 (Text Search) 簡易功能測試
三月份 MongoDB 推出了 2.4 版,在其 Release Note 中,最大的亮點就是正式推出了 全文搜尋 (Text Search) 功能。
雖然看 Text Search 支援的語言列表中,並不支援中文,但因為搜尋是個我很感興趣的功能,所以還是來試玩看看,整理一下簡單的測試過程。
1. 到官網下載最新版的 MongoDB 2.4
2. 使用以下指令,啓動 MongoDB
啓動的參數需加上 --setParameter textSearchEnabled=true,以告訴 Server 要啓用 Text Search 功能。
$ mongod --bind_ip 0.0.0.0 --dbpath=D:\tmp\FullTextTest --rest --setParameter textSearchEnabled=true
3. 新增測試資料
3.1 準備測試資料
我準備了一個資料檔 insert.js,在 News collection 下新增測試的文件,每個文件有兩個欄位︰標題 (Subject) 和內文 (Content)。
因為只是簡易測試,所以我只有簡單整理三篇文章,其中兩篇是中文的新聞,另一篇是英文的 Blog 文章。當然如果未來想增加測試文件的數量,只要修改這個檔,加入文件內容就可以了。
內容如下︰
var docs = [{ "Subject": "「九宮格」都會利用 鐵捕讚建仔:真的不一樣", "Content": "中國時報【(執筆:楊舒媚、江慧真)】 中華隊當家鐵捕高志綱對旅外巨投王建民的球有什麼評價?回首2002年釜山亞運,王建民是小聯盟球員,高志綱接建仔的球直覺地說,「哇,應該是大聯盟級的啊,還是大聯盟選手真是高過想像,這樣的程度都還沒辦法上去?」這次經典賽正面迎向王建民,高志綱更驚艷,「在大聯盟待過,他投球的感覺真的不太一樣。」建仔不太需要配球,「只要讓他自己保持好的節奏,結果不會差太多。」高志綱讚嘆,王建民的伸卡球太好了,且很清楚地知道怎麼去利用好球帶位置,「不是邊邊角角兩邊投得準就好,是九宮格都會利用,光是直球,就懂得用不一樣的角度嘗試。」對於郭泓志,「台灣出來的選手找不到像他這麼霸氣的!」高志綱形容,但小小郭上來時,多半是緊急狀況或球賽後半,「要慎重一點,讓他投得更有自信。」" },{ "Subject": "MLB/黑田博樹展大和魂 受傷仍不想被取代", "Content": "記者陳浚錡/綜合報導 洋基隊日籍右投黑田博樹4日遭球擊中右手中指,即便接受初步檢查沒有大礙,第二天他仍感到有點不舒服,但他意志堅強不想被取代,接受訪問時表示,還是希望能夠趕上第二場先發。黑田第一戰先發對紅襪狀況不理想,首局就挨3支安打掉1分,2局上維多里諾(Shane Victorino)擊出投手前強襲球,黑田本能反應想伸出右手接,結果打到中指,雖然一開始還待在場上,但一連串的保送後仍被迫退場休息,而且表情痛苦。經過X光檢查以及斷層掃描,黑田手指骨頭並沒有斷,但他還是覺得有些不適,「我得說感覺並不尋常,打到的地方還是有點不舒服,我認為球打到手本來就會痛了。」這位38歲投手表示,他目前的底線就是要趕上下一場先發,不過還是會在6日進牛棚練投後確定狀況。黑田近兩季皆全勤出賽,投球局數更超過200,是洋基隊倚重的長局數先發投手,若他傷得比想像中嚴重,投手輪值勢必元氣大傷。不管黑田能不能投,洋基已經先做好備胎,由前役投5.1局失1分的右投華倫(Adam Warren)待命,去年是華倫大聯盟的第一年,但也只出賽1場,先發僅投2.1局被擊出8支安打,包括2支全壘打失6分,最後靠著洋基堅強打線逃敗。" },{ "Subject": "MongoDB Full Text Search", "Content": "Yesterday we released the latest unstable version of MongoDB; the headline feature is basic full-text search. You can read all about MongoDB's full text search in the release notes.This blog had been using a really terrible method for search, involving regular expressions, a full collection scan for every search, and no ranking of results by relevance. I wanted to replace all that cruft with MongoDB's full-text search ASAP. Here's what I did." }]; db.News.insert(docs);
3.2 執行新增動作
在命令列下,執行以下指令,在 TextSearchTest DB 下,新增測試文件︰
$ mongo 127.0.0.1:27017/TextSearchTest insert.js
到此,我們已經將 MongoDB 的測試文件給建立好了。接下來就是建立全文索引,和測試搜尋的功能。
4. 建立全文索引
4.1 連接 DB
我們以下指令,連接 TextSearchTest DB︰
$ mongo 127.0.0.1:27017/TextSearchTest
4.2 建立全文索引
在 MongoDB 下,以下列指令 ,建立 News collection 的全文索引︰
db.News.ensureIndex( { Subject: "text", Content: "text" } );
5. 搜尋測試
再建立全文索引後,接下來就可以正式做搜尋功能的測試了,搜尋功能非常簡單,以要對 News collection 搜尋為例,搜尋指令如下︰
db.News.runCommand( "text" , { search: "搜尋關鍵字 " } )
我們分成英文與中文的功能測試︰
5.1 英文測試
簡單搜尋一下 MongoDB 這個詞︰
db.News.runCommand( "text" , { search: "MongoDB" } )
可以看到,結果是我們所預期的,最後一篇關於 MongoDB 的 blog 文章。
5.2 中文測試
我們再針對 王建民 做搜尋︰
db.News.runCommand( "text" , { search: "王建民" } )
結果則是無法正確的搜尋,我們所預期的第一篇中文新聞沒有顯示在搜尋結果中。
本文針對 MongoDB 新推出的 Text Search 功能,做一個非常簡單的功能測試。就目前測試的結果,要將 MongoDB 做為中文文件的搜尋引擎,現階段應該還是沒有辦法。或許後續的版本會再將中文搜尋納入支援的語言也不一定。