1. 該如何學好 "寫程式" ??

    會寫這篇是因為上禮拜,有個資深的同事問我個問題,如何把底下的 programmer 素質拉上來? 跟他講這問題害我那天拖到晚上十點才回家吃晚飯 @_@,不過我想這也是現在台灣軟體業普遍碰到的人才問題,就順手寫了這篇。這篇是打算要貼在公司的 BLOG 裡給同事看的,這裡先貼一下,到時整理好再搬過去...。

     

     

    -----------------------------------------------------------------------------------------

    其實在這之前,一直有人問我這類問題,我的回答總是一樣: "要從基本功做起。"   只不過大部份人都會皺皺眉頭,想說 "為什麼沒有速成一點的方法? "

    先撇開軟體開發一定要有其它領域的 domain know-how 之類的東西,我就針對到底有沒有辦法把 CODE 寫好這件事來討論。如果真的存在速成的方法,那滿街就都是高手強者了。技術及工具的進步,是簡化你操作工具及開發過程的細節,但是其中累積的知識及理論,只會越來越多

    現在要寫程式所必須俱備的技術門檻越來越低,但是要能專精所必要的知識及經驗則是越來越高。我常在想,我是一路學習摸索上來的,大學四年唸了電機學到了硬體的底子,大三大四本來想修資工當輔系,後來直接去考資工所... 這三四年下來也有點資訊的底子,這些知識讓我到現在都還不用辛苦的去 "追" 技術,我只要看看 overview,大概就能掌握這些技術能解決什麼問題,不能解決什麼問題等等。真正需要用的時後,文件翻一翻就能找到我要的段落,就能夠上手了。

    不過現在的年輕人就沒那麼好運了,要從 CPU 是怎麼設計,怎麼執行指令,到瀏覽器為什麼點下網址就能看到網頁,這一連串的細節,大概現在的大學畢業生都回答不出來吧? 現在隨便買本書翻一翻,就能寫出一個漂亮的網站,誰還願意去唸那些基本功? 也因為這樣現在的人都少了那份 "內功",只剩漂亮的招式,出招很勵害,不過打沒兩下就後繼無力,或是對手出了沒看過的招,就不知怎麼接下去了。

    如果你真的有心把底子練好,我是有幾個建議的方向,雖然看起來沒什麼用,但是看熟了你一定會發現,你寫什麼程式都逃不了這幾個基礎知識。

    1. 最基本的: 計算機概論 & 資料結構
      這些有助於你用正確的邏輯寫程式。要成為一個合格的 programmer 一定要有這樣的能力。
    2. 進階一點的系統層面,作業系統 & 系統程式
      這些有助於你瞭解系統層面如何運作,如果你開發的系統需要些基礎建設,像是元件等等,這些知識很有用。成為 software engineer 就應該要有這些基礎。
    3. 再來就專精一點了,我推薦 OOP 理論 / Design Patterns、或是軟體工程的方法論 ( XP, TDD ... 等 )
      這個層次的知識能幫助你設計正確的架構,或是用正確的方式開發軟體,是成為 ARCHITECT 的必要技能。

    每一項都代表一個階段。上禮拜跟同事討論的,其實只有討論到 (1) 的部份。如果工程師都 "" 寫程式,但是用的邏輯看起來都 "怪怪的",那就是要加強 (1) 的部份了。我簡單的舉個例子,資料結構在學什麼? 跟實際寫程式能有什麼關聯?

    想到資料結構,不外忽一堆排序 (SORT) 的演算法,或是各種 TREE / LIST 等怎麼 "放" 資料,及怎麼 "找" 資料的問題,如 LINKED LIST,HASH TABLE,BINARY TREE,HEAP,STACK 等等。再來就是什麼問題可以用什麼資料結構來處理? 像是走迷宮要靠 STACK,各種資料結構的特性為何? 它們的時間複雜度 (Time Complexity) 為何? 什麼時後該用那一種?

    這些是很基礎的問題,不過你如果不是科班的,只是翻翻書就會寫程式的,那這些問題應該都回答不出來吧? 針對這部份,我強烈建議要學的人一定要先搞懂這些 "邏輯"。我不稱為理論,是因為他們還太淺,只是個作法而以。搞懂這些邏輯,你至少要有能力把程式寫出來。

    因此第一課很簡單,挑幾種 SORT 的方式,比如 Bubble Sort, Quick Sort 等等,不用多,兩三個就好,步驟搞清楚了,還能用你熟悉的程式寫出來 ( 如: C# / JavaScript,當然你不能作弊用現成的 SORT 函式庫 ),你就過關了。

    再來就是搞懂各種資料結構,我舉幾個 .NET 內建的,卻又常讓人搞混的幾個 Collection。List / LinkedList 用的方式都一樣,那麼兩者到底有什麼不一樣? 只塞一百筆,找出一筆要 10 ms 的話,塞一萬筆找出一筆要花多少時間? 是 100 倍嗎? 還是 10 倍? 還是都一樣?? 是 Microsoft 工程師太無聊,故意寫來讓你傷惱筋的嗎?

    如果這部份你也搞懂了,接下來就是應用了。就拿導航系統來說就好,地圖要用什麼方式存才好? 使用者選定起點及終點,你該怎麼幫它找出最佳的路逕? 不管畫面等等問題,你有辦法寫出程式找到答案嗎? 這就是典型的資料結構的應用。你沒學好資料結構的話,看再多 C# / ASP.NET 的書,一點用都沒有啦,碰到這類問題,管你用 VB / C++ / C# 還是  Java, 只能坐在螢幕前發呆而以。

     

    總結一下,你符合我講的 (1) 基本要求嗎? 很簡單,這些問題或程式你都寫的出來就符合了:

    • 丟一付洗過的撲客排給你 (不要多,黑桃1 ~ 13就好),你知道怎麼用 Bubble Sort / Quick Sort 的步驟把它排好嗎? 丟一個陣列,裡面隨便打幾個數字,你能寫程式把它由小到大排好印出來嗎?
    • 假設記憶體夠大的話,你有辦法把一百萬筆通訊錄資料讀到記憶體內 (用什麼物件都隨你),然後還能用很快的速度找到你要的資料嗎? 不同的搜尋方式,你知道該用什麼樣的方式找才有效率嗎?
    • 以台灣高速公路為題 (中山高、北二高、國道二號),你有辦法寫程式,讓使用者指定起點跟終點的交流道,然後替它找出建議的路線嗎? (把延路經過的交流到跟收費站列出來就好)

    看起來就像是作業,沒錯。不過它是很實際的基本功夫,如果寫不出來,那就真的該好好唸個書了。其實這些問題,都跟熱門的技術 (如 DB / WEB / RIA 等等) 無關,就很單純的看你的邏輯能力而以。這個主題我會繼續寫下去,大概一兩個禮拜一篇吧。我的目的是希望大家底子打好再來學這些熱門技術,這樣你才有辦法更進一階,否則就只能隨著技術規格推陳出新,不斷的在追新技術而以。上面那三個問題,有興趣的話歡迎找我聊聊。在這留話或是 mail 給我都可以。

    2008/09/27 系列文章: 如何學好寫程式 專欄 技術隨筆 有的沒的

  2. 令人火大的 SQL 字元編碼...

    今天同事有個問題搞不定,就找我去處理,越弄越火... 嘖嘖,這裡就來還原一下現場,記錄一下這個鳥問題...。

    最討厭處理這種問題了。現在客戶 IT 都委外,結果就變成 IT 本身不大管事,什麼事就交給外包商就好... 而這種 A 包商跟 B 包商之間的問題,往往就變成踢皮球... 除非有明確證據指出人就是他殺的,不然? 出問題的一方自己摸摸鼻子吧。幾年來吃了不少這種虧,這就是小廠的悲哀啊。

    這次碰到的例子是客戶 A 系統建的資料,需要整理後匯到我們負責維護的 B 系統。而中間資料需要作些修正,所以建了一個中繼資料庫,透過 LINKED SERVER,從 A 系統的 DB 把整個 TABLE SELECT 一份到中繼資料庫,然後再進行一連串的修正...

     

    image

    一開始問題很單純,就兩邊碰到中文字編碼不同,直接 SELECT 就碰到這樣的亂碼..

     

    image

    看起來是個小問題,請對方 IT 確認了編碼的問題後,我在中繼資料庫作了點調整,convert 成 ntext 就搞定了。已經可以跑出正確的中文資料了。

    正想把程式弄一弄就收工,然後很得意的回報問題搞定時,發現不大對勁,怎麼整個 BATCH 跑下來結果還是錯的? 還錯的不一樣? 真是奇了... 原本轉 UNICODE 問題,再怎麼樣也應該只是變亂碼,或是變 ? 而以,結果這次看到的是資料錯亂,跑出其它的字出來...

     

    image

    這張圖是我把問題簡化後抓到的,原本是有上百行的 SQL SCRIPT ... @_@ 被我抽絲撥繭剩這段。第 33 筆資料是有問題的,不過出現的資料不是原本 "馥瑈" 啊,原本是第二個字變成 ? 而以... 現在竟然變成上一筆資料 (第32筆) 的內容,而第三個字 '榮' 則是由前面好幾筆的 "XX榮" 留下來,第四個字 "子" 就真的不曉得從那裡來了...

     

    好怪的問題,如果是程式碰到這種 BUG,一看就像某個 BUFFER 沒清掉就一直重複被使用,後面的值直接蓋掉前面的值,字串長一點沒被蓋掉,就這樣留下來了。而到了第 33 這筆不知為什麼原因,整個 BUFFER 的內容就跑出來... 這段 SQL SCRIPT 跟本沒作什麼事,不過是前面的 SELECT 指令,改 SELECT INTO 到 TEMP TABLE,然後再撈出來而以。直接 SELECT 沒問題,沒道理 SELECT INTO 就掛掉啊! 不過還真的被我碰到了,see the ghost ..

     

    我沒有直接拼下去 GOOGLE 或是試各種解法,而是想了一下問題是怎麼回事? SQL / DB 的東西我只算外行人,沒本事跟他硬碰硬,我也不知道有啥工具可以看 SQL NATIVE CLIENT 的 TRACE INFO 之類的,只能靠想像,猜一下問題會在那。一開始我就否定掉是不是什麼編碼或是定序的問題,因為我可以正確的 SELECT 出來啊,而且如果 SELECT INTO 是變成 ?? 的話我也許還會頭痛一點,不過竟然是上一筆的資料跑過來了? 這個問題很明顯的,跟本就是 overflow (緩衝區溢位) 之類的 BUG。八成是什麼地方應該填個 0x00 作字串結尾的 CODE 錯掉,結果 SQL 就抓過頭,抓到不該抓的舊資料才會這樣。

     

    拿我寫了十幾年程式的經驗,跟它賭下去了!!  於是我就沒再去跟一堆編碼定序之類的設定搏鬥了,因為我認定這是 SQL SERVER 或是 SQL NATIVE CLIENT 的 BUG。我採的方案是找可以繞過去的方式,於是我把我想的到的 COPY TABLE 都用上了,最後試出來的是笨方法...

     

    用... 用... 用 CURSOR 一筆一筆的跑... @_@

     

    哈哈,各位看倌請笑小聲一點,我這貴州的驢子就只會這種把戲而以.... 果然這樣就正確了。CURSOR 的 FETCH 指令,把欄位的值抓到 nvarchar 的變數,就一切 OK,然後再把這變數的值 update 回我的暫存 TABLE,就什麼問題都沒了 -_-

     

    真是它ㄨㄨㄨㄨ的,最後的 SOLUTION 細節我就不一一的貼出來了,反正只是換個方式 COPY TABLE 而以。既然我賭是 Microsoft 的問題,而換條路的方式 (完全沒改任何定序 OR 編碼) 也成功了,我就不跟它奮鬥下去了,客戶的 IT 既然懶的上 UPDATE / SERVICE PACK,我也只好避掉問題了事... 咳咳。特地貼出來記念一下,如果你們也碰到一樣的問題,切記切記,別跟它硬碰硬啊... :D

    2008/09/27 SQL Tips 火大

  3. [C# yield return] #2. 另類的應用 - Thread Sync 替代方案

    上篇,講了一些 yield return 編譯後產生的 Code, 說明了 C# compiler 如何用簡單的語法替你實作了 IEnumerator 介面,而完全不會增加程式的複雜度,這是我認為 C# 提供最讚的 Syntax Sugar ...。 不過無意間我想到了 yield return 還有另一種應用方式。靈感來自之前 Darkthread 舉辦的 [黑暗盃程式魔人賽]。因為參賽題目 [xAxB猜數字遊戲] 原本就是考驗演算法,邏輯就不大簡單了,加上要配合 GameHost 的呼叫方式,難度更提高不少。因此之前貼了兩篇文章 [ThreadSync #1. 概念篇 - 如何化被動為主動?, #2. 實作篇 - 互相等待的兩個執行緒],介紹了我改寫的 AsyncPlayer,讓程式可以分別以獨立的執行緒執行 GameHost 及 Player 的程式碼。藉著這方式讓兩者都可以 "獨立思考",邏輯不會中斷,讓程式能夠簡單一些。 不過執行緒同步機制是很花時間的,因為兩方都要等來等去...。多了 Sync 的動作,就要至少 10 ms 的時間來完成這動作。跑個十幾萬次下來,額外花費的時間太多了,因此我貼了那兩篇文章後,就一直在思考這樣的作法有沒有其它效能較佳的方式? 有的,最後我找到的答案就是 yield return,不過大家看了一定很納悶... "yield return (Iteration) 跟執行緒同步機制有什麼關聯?" 不多說,先看看之前畫的兩張時序圖: image 先看之前 ThreadSync #1 裡提到的圖,我這次加上紅線當 "輔助線",紅線代表執行 GameHost 的主程式,這個執行序必需反反覆覆的在 GameHost / Player 兩份類別的程式碼跑來跑去,主程式是 GameHost 發起的,當然被強迫切成好幾段的就只有 Player 了。 image 這是修改過後的版本,GameHost / Player 有各自的執行緒,紅色是 GameHost,藍色是 Player。當執行緒跑到中間時代表它在等待了,等另一方也跑到中間把執行結果放到共用變數,同時叫醒對方之後才交換過來。兩方都各自照著自己的邏輯跑,不過這種等待 & 喚醒的動作,相較於一般的 function call / return 而言,實在是太慢了...。我就是從這張圖得來的靈感,這個解決方式不就跟 yield return 很像嘛? 都是為了避免多次呼叫之間,被呼叫的另一方的邏輯被破切斷的問題... 因此我就開始思考 AsyncPlayer 是不是有機會用 yield return 寫出另一個版本...。 原本的結構很直覺,透過共用變數來傳遞資訊,用 AutoResetEvent 來通知另一個等待中的執行緒可以醒來拿資料去用。而 yield return 則要換個角度來想這件事。yield return 是實作 Iterator 的一種方式,目的是讓你的程式自己決定如何把 collection 裡的 element 照什麼方式丟出去,原本的問題就要想成: "GameHost 要跟 Player 拿所有 Player 會問的問題,而 Player 會透過 yield return 一次一次的把問題丟出去。" 看起來好像可行,不過方向只有單向,就是 Player 丟問題給 GameHost,還缺了 GameHost 把問題答案交給 Player 這段。不過這部份好解決,一樣用共用變數就搞定。細節我就不講太多,直接來看程式碼: 用 yield return 改寫過的 AsyncPlayer

            public abstract IEnumerable<HintRecord> Think();
    
            private HintRecord last_record = null;
    
            public override int[] StartGuess(int maxNum, int digits)
            {
                base.StartGuess(maxNum, digits);
                this._enum = this.Think().GetEnumerator();
                this._enum.MoveNext();
                return this._enum.Current.Number;
            }
    
            public override int[] GuessNext(Hint lastHint)
            {
                this._enum.Current.Hint = lastHint;
                if (this._enum.MoveNext() == true) return this._enum.Current.Number;
                throw new InvalidOperationException("Player Stopped!");
            }
    
            public override void Stop()
            {
                base.Stop();
                this._enum.Current.Hint = new Hint(this._digits, 0);
                try { this._enum.MoveNext(); }
                catch {
                    Console.WriteLine("!!!!");
                }
            }
    
            protected virtual HintRecord GameHost_AskQuestion(int[] number)
            {
                this.last_record = new HintRecord(
                    (int[])number.Clone(),
                    new Hint());
                return this.last_record;
            }
    
            protected HintRecord GameHostAnswer
            {
                get
                {
                    return this.last_record;
                }
            }
    
    程式碼一如往常,又是只有一點點 (謎之音: 你到底有沒有寫過長一點的程式碼? -_-) ...。 原本的 Think 改成會傳回 IEnumerable<HintRecord> 的型別,因此內部就可以透過一連串的 yield return xxxx; 指令來把問題交給 GameHost。而 GameHost 拿到題目就會開始計算答案,然後再呼叫 Player.GuessNext( ) 把上次的答案傳回去。透過 Player 的實作,GuessNext 會呼叫 _enum.MoveNext( ), 控制權會再交到 Think( ) 上次呼叫 yield return 的地方,直到又執行到下一個 yield return 為止。這時 GameHost 又取得下一個問題,不斷重複這樣的動作直到結束。 同樣的,我們用 DummyPlayer 改寫,看看用 yield return 的版本寫起來是怎麼樣? DummyYieldPlayer 的程式碼
        public class DummyYieldPlayer : YieldPlayer
        {
            private Random _rnd = new Random();
            private int[] randomGuess()
            {
                int[] _currAnswer = new int[this._digits];
                List<int> lst = new List<int>();
                for (int i = 0; i < _digits; i++)
                {
                    int r = _rnd.Next(_maxNum);
                    while (lst.Contains(r))
                        r = _rnd.Next(_maxNum);
                    lst.Add(r);
                    _currAnswer[i] = r;
                }
                return _currAnswer;
            }
    
            public override IEnumerable<HintRecord> Think()
            {
                while (true)
                {
                    yield return this.GameHost_AskQuestion(this.randomGuess());
                }
            }
        }
    
    跟上次的 DummyAsyncPlayer (用 ThreadSync 的版本) 一樣,超簡單,實在沒什麼需要說明的了。唯一要特別記得的是,如果你需要取得 GameHost 傳回的答案,應該在 22 ~ 23 行之間,使用 this.GameHostAnswer( ) 來取得答案。有人問我為什麼不把它包成 function call ? 在 function 內接到參數後呼叫 yield return, 而把答案 return 回來不是很好嗎? 很無奈,除非 C# 支援像 C/C++ 那樣的 MACRO 語法,不然這個東西是不可能單靠 yield return 就做出來。你使用 yield return 的條件就是 function return type 一定要是 IEnumerable<T>,這是配對的,代表你不能任易的把 yield return 移到其它 function call 內。除非你不靠 C# yield return 來自動產生對應的 IEnumerator,一切自己來就可以。不過這樣不就又回到原點了? 咳咳... 就乖乖的寫兩行吧。 這樣的寫法執行效率就好的多,我用 DummyYieldPlayer 來測試,跟 DarkThread 提供的版本不相上下,意思是差異小到可以不理它的地步了 :D 這樣的方式不會有太大的效能損失,因為最後要執行的程式碼,跟直接手寫是差不多的,只是中間難寫的那段 code 是 C# compiler 幫我們解決掉,而不是像上回 AsyncPlayer 是用兩個執行緒來解決的。 效果很滿意,當然最後參賽的版本就改這寫法了 :D。不過寫的太晚,來不及幫到其它參賽者 :P,想到這方法算是我佔了 C# compiler 一點便宜,有幸找到方法坳 C# compiler 幫我把最難的部份寫好了,我自己則樂的輕鬆,專心研究怎樣才能少猜幾次... 這裡把我另類應用 yield return 的方法貼給各位參考一下,也算作個筆記 :D,各位高手如果還有發現 yield return 解決過你什麼樣的怪問題,也歡迎到我這留個言 :D

    2008/09/22 系列文章: Inside C# Yield Return .NET C# Tips 作品集 多執行緒 技術隨筆 有的沒的

  4. [C#: yield return] #1. How It Work ?

    C# 常常拿來跟 Java 比較,在 .NET 1.1 時常常是不相上下,而 .NET 又因為較年輕 & 頂著 Microsoft 的名號,往往被當成玩具一樣,不過 Microsoft 的確是在 .NET 及 C# 下了很多功夫,作了很多 Sun 不願意在 Java 身上作的事,這次要探討的 yield returnIEnumerable<T> 這搭配的 Interface 就是一例...。 Java 在過去的版本,往往為了跨平台,把修改 VM 規格視為大忌,連帶的連語法修改都一樣,即使不影響編譯出來的 bytecode 相容性也是一樣不肯改。而 .NET 就為了語法簡潔,編譯器往往是讓步的一方,因此 C# 有相當多的 Syntax Sugar,讓你寫起 CODE 爽一點...。你只要寫簡單的 CODE,編譯器會幫你轉成較煩雜的 CODE,有點像是文言文或是成語那樣的味道。古代文人常常用簡單的四個字,就有一大票引申的意義跑出來...,寫作文時只要套上成語,就代表了成語背後的故事,寓意。 "yield return" 算是最甜的甜頭了,因為編譯器替你翻出來的 code 整整一大串。先來看個簡單的例子,如果我想實作 IEnumerator<T> Interface, 照順序輸出 1 ~ 100 的數字,正統的 C# code 看起來要像這樣: 用 IEnumerator 依序傳回 1 ~ 100 的數字

        public class EnumSample1 : IEnumerator<int>
        {
            private int _start = 1;
            private int _end = 100;
            private int _current = 0;
    
            public EnumSample1(int start, int end)
            {
                this._start = start;
                this._end = end;
                this.Reset();
            }
    
            public int Current
            {
                get { return this._current; }
            }
    
            public void Dispose()
            {
            }
    
            object System.Collections.IEnumerator.Current
            {
                get { return this._current; }
            }
    
            public bool MoveNext()
            {
                this._current++;
                return !(this._current > this._end);
            }
    
            public void Reset()
            {
                this._current = 0;
            }
        }
        好不容易寫好 IEnumerator 之後,再來是拿來用,一筆一筆印出來:
    取得 IEnumerator 物件後,依序取出裡面的數字
                EnumSample1 e = new EnumSample1(1, 100);
    
    
    
                while (e.MoveNext())
                {
                    Console.WriteLine("Current Number: {0}", e.Current);
                }
    
      不過如果只是要列出 1 ~ 100,大部份的人都不會想這樣寫吧? 直接用計概第一堂教你的 loop 不就好了? 程式碼如下:
    送分題: 用 LOOP 印出 1 ~ 100 的數字
                for (int current = 1; current <= 100; current++)
                {
                    Console.WriteLine("Current Number: {0}", current);
                }
    
        兩個範例都沒錯啊,那為什麼要用 IEnumerator ? 其實 IEnumerator 並不是 Microsoft 發明的,在四人幫寫的經典書籍 (Design Patterns) 裡就有這麼一個設計模式: Iterator,它的目的很明確: "毋須知曉聚合物件的內部細節,即可依序存取內含的每一個元素。" (摘自 物件導向設計模式 Design Patterns 中文版,葉秉哲 譯) 這裡指的 "聚合物件" 就是指 .NET 的 Collection, List, Array 等這類物件。意思是你不需要管 collection 裡每一個物件是怎麼擺的,用什麼結構處理的,用什麼邏輯或演算法處理的,我就只管照你安排好的順序一個一個拿出來就好。沒錯,這就是它主要的目的。換另一個說法,就是我們希望把物件巡訪的順序 (iteration) 跟依序拿到物件後要作什麼事 (process) 分開,那你就得參考 Iterator Pattern。不用? 那只好讓你的 iteration / process 混在一起吧。 差別在那? 我們再來看第二個例子。如果題目改一下,要列出 1 ~ 100 的數字,但如果不是 2 的倍數,也不是 3 的倍數,就跳過去。先來看看 Loop 的版本:
    進階送分題,用LOOP印出 1~100 之中,2 或 3 的倍數
                for (int current = 1; current <= 100; current++)
                {
                    bool match = false;
    
                    if (current % 2 == 0) match = true;
                    if (current % 3 == 0) match = true;
    
                    if (match == true)
                    {
                        Console.WriteLine("Current Number: {0}", current);
                    }
                }
    
        再來看看 IEnumerator 的版本:
    用 IEnumerator 列出 1 ~ 100 中 2 或 3 的倍數
       public class EnumSample2 : IEnumerator<int>
        {
            private int _start = 1;
            private int _end = 100;
            private int _current = 0;
    
            public EnumSample2(int start, int end)
            {
                this._start = start;
                this._end = end;
                this.Reset();
            }
    
            public int Current
            {
                get { return this._current; }
            }
    
            public void Dispose()
            {
            }
    
            object System.Collections.IEnumerator.Current
            {
                get { return this._current; }
            }
    
            public bool MoveNext()
            {
                do {
                    this._current++;
                } while(this._current %2 > 0 && this._current %3 > 0);
                return !(this._current > this._end);
            }
    
            public void Reset()
            {
                this._current = 0;
            }
        }
        而扣掉 IEnumerator 的部份,要把數字印出來的程式碼則完全沒有改變:
    取出 IEnumerator 的每個數字,印到畫面上
                EnumSample2 e = new EnumSample2(1, 100);
    
    
    
                while (e.MoveNext())
                {
                    Console.WriteLine("Current Number: {0}", e.Current);
                }
    
      可以看的到,Loop 版本的確是把 iteration 跟 process 的 code 完全混在一起了,未來任何一方的邏輯要抽換都很麻煩,而 IEnumerator 則不會,分的很清楚,不過... 這 Code 會不會太 "髒" 了一點啊...? 試問一下,有誰會這麼勤勞,都用 IEnumerator 來寫 Code? 有的話請留個言,讓我崇拜一下...。   屁話講了一堆,最後就是要帶出來 "的確有魚與熊掌得兼的方法",怎麼作? 來看看用 C# 的 yield return 版本的程式碼:
    傳回 IEnumerable 的 METHOD (不用再寫 CLASS,實作 IEnumerator 了)
            public static IEnumerable<int> YieldReturnSample3(int start, int end)
            {
                for (int current = 1; current <= 100; current++)
                {
                    bool match = false;
    
                    if (current % 2 == 0) match = true;
                    if (current % 3 == 0) match = true;
    
                    if (match == true)
                    {
                        yield return current;
                    }
                }
            }
       
    用 foreach 搭配 IEnumerable 印出每一筆數字
                foreach (int current in YieldReturnSample3(1, 100))
                {
                    Console.WriteLine("Current Number: {0}", current);
                }
    
          真是太神奇了,安德魯。如何? 完美的結合兩者的優點,這種 code 實在是令人挑不出什麼缺點... 真是優雅... 不過念過系統程式的人一定都會吶悶... 這樣的程式執行方式,不就完全的違背了一般結構化程式典型的 function call / return 的鐵律了? 程式呼叫某個 function 就應該完全執行完才能 return 啊,怎麼能 "yield" return 後,跑完一圈又回到剛才執行到一半的 function 繼續跑,然後再 "yield" return ? 好像同實有兩段獨立的邏輯在運作... 還可以在兩者之間跳來跳去? 這就是 C# compiler 猛的地方了。搬出 reflector 來看看編譯出來的 code, 再被反組譯回來變成什麼樣子:  
    反組譯 YieldReturnSample3
    public static IEnumerable<int> YieldReturnSample3(int start, int end)
    {
        <YieldReturnSample3>d__0 d__ = new <YieldReturnSample3>d__0(-2);
        d__.<>3__start = start;
        d__.<>3__end = end;
        return d__;
    }
    
     
    
      耶? 看到一個多出來的 class: <YieldReturnSample3>d__0 ... 再看看它的 class 長啥樣:
    編譯器自動產生的 IEnumerator 衍生類別
    [CompilerGenerated]
    private sealed class <YieldReturnSample3>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
    {
        // Fields
        private int <>1__state;
        private int <>2__current;
        public int <>3__end;
        public int <>3__start;
        private int <>l__initialThreadId;
        public int <current>5__1;
        public bool <match>5__2;
        public int end;
        public int start;
    
        // Methods
        [DebuggerHidden]
        public <YieldReturnSample3>d__0(int <>1__state)
        {
            this.<>1__state = <>1__state;
            this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
        }
    
        private bool MoveNext()
        {
            switch (this.<>1__state)
            {
                case 0:
                    this.<>1__state = -1;
                    this.<current>5__1 = 1;
                    while (this.<current>5__1 <= 100)
                    {
                        this.<match>5__2 = false;
                        if ((this.<current>5__1 % 2) == 0)
                        {
                            this.<match>5__2 = true;
                        }
                        if ((this.<current>5__1 % 3) == 0)
                        {
                            this.<match>5__2 = true;
                        }
                        if (!this.<match>5__2)
                        {
                            goto Label_0098;
                        }
                        this.<>2__current = this.<current>5__1;
                        this.<>1__state = 1;
                        return true;
                    Label_0090:
                        this.<>1__state = -1;
                    Label_0098:
                        this.<current>5__1++;
                    }
                    break;
    
                case 1:
                    goto Label_0090;
            }
            return false;
        }
    
        [DebuggerHidden]
        IEnumerator<int> IEnumerable<int>.GetEnumerator()
        {
            Program.<YieldReturnSample3>d__0 d__;
            if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
            {
                this.<>1__state = 0;
                d__ = this;
            }
            else
            {
                d__ = new Program.<YieldReturnSample3>d__0(0);
            }
            d__.start = this.<>3__start;
            d__.end = this.<>3__end;
            return d__;
        }
    
        [DebuggerHidden]
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
        }
    
        [DebuggerHidden]
        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }
    
        void IDisposable.Dispose()
        {
        }
    
        // Properties
        int IEnumerator<int>.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }
    
        object IEnumerator.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }
    }
    
      耶? 不就完全跟之前手工寫的 IEnumerator 一樣嘛? 只不過這個 IEnumerator 是自動產生出來的,不是手寫的...。 畢竟是機器產生的 CODE,總是沒那麼精簡。想到了嗎? 沒錯,這就是 C# compiler 送給你的 syntax sugar ...,你可以腦袋裡想像著計概課入門時教你的 LOOP 那樣簡單的想法,compiler 就幫你換成 IEnumerator 的實作方式,讓你隨隨便便就可以跟別人宣稱:   "看! 我的程式有用到 Iterator 這個設計模式喔..."   聽起來好像很臭屁的樣子... 哈哈! 如果是在真的用的到 Iterator Patterns 的情況下,真的是可以很臭屁的拿出來炫耀一下。不過,我幹嘛突然講起 yield return ? 各位看的過程中有沒有聯想到前幾篇 POST 講的 Thread Sync 那兩篇文章 ( #1, #2 ) ? IEnumerator 跟 Thread Sync 又有什麼關係? 賣個關子,下篇繼續!

    2008/09/18 系列文章: Inside C# Yield Return .NET C# Tips 多執行緒 技術隨筆 物件導向

  5. 莫明奇妙的錯誤訊息: 找不到 VJSharpCodeProvider ?

    話說前陣子處理了 BlogEngine.NET 升級到 1.4.5.0,另外也寫了 SecurePost.cs 這個 extension, 其時都碰過這個鳥問題,只是一直沒去理它而以。接下來為了要改 PostViewCounter.cs (BE extension, too), 又碰到... 於是就認真的研究了一下...。

    過程是這樣,為了建立 BlogEngine 的開發環境,首先我從官方網站下載了 source code, 解開後編譯都沒問題,OK。

    接下來 WEB 的部份我把網站上的 source code 搬過來 (不包含 ~/App_Data,太大了),編譯也 OK。

    不過我要改 Counter 的 Code 啊,沒有一些 SAMPLE DATA 很難測試,只好把資料檔也搬過來.. 結果 Visual Studio 2008 就冷冷的回了這訊息給我:

    (0): Build (web): The CodeDom provider type "Microsoft.VJSharp.VJSharpCodeProvider, VJSharpCodeProvider, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" could not be located.

     

    我沒有漏貼前面的訊息... 的確是沒有檔名,也沒有行號(0)。我最不能忍受的就是沒頭沒尾的 ERROR MESSAGE 了。除了告訴你 "掛掉了" 之外,無頭無腦的對於追查問題實在沒什麼幫助。只好靠自己了...。雖然這是個 compile error message,不過我要 RUN 的畢竟是個 web site, 不編譯也是可以跑,除了那個惱人的錯誤訊息之外,要執行倒是沒問題。只不過編譯失敗,我就不能設中斷點,直接 F5 執行測試。雖然可以另外手動 Attach Process 的方式來除錯,不過每次都要這樣搞實在是很煩..

     

    仔細想了想,沒錯,我是沒裝 Visual J#。不過我的確沒要用 Visual J# 啊,如果真的用到 J# 的話,出這訊息是應該的。訊息沒有原始檔? 也沒有錯誤行號? 那問題應該是 Global 的範圍,第一個想到的就是 web.config 是不是定義了 CodeDom 或是指定了相關的 CodeProvider ? 無奈查了一遍沒看到,VS2008 的 PROJECT 設定也沒看到引用任何 J# 相關的 LIB...

     

    已經到了死馬當活馬醫的地步... 開始亂找一通碰碰運氣。搜尋了一下有沒有 *.java 的檔? OUCH,還真的有... 在 ~/App_Data/files 下找到我古董檔案,研究所時代寫的 Java Applet .... 順手試一下,刪掉後還真的就過了? 這個無頭無腦的問題,就在不知不覺中找到 solution, case closed!

     

    怒... 這樣也算? 找到 .java 的程式碼,去找 VJ# 來編譯還說的過去,不過找 "source code" 找到 ~/App_Data 實在是太超過了一點... 好歹也列個要編譯那個檔案,然後找不到對應的 CodeProvider,這樣要排除問題也簡單一點...

     

    結論是: 各位別太鐵齒,看來 ~/App_Data 下的檔案也是不能亂塞的...

    2008/09/10 .NET ASP.NET BlogEngine.NET Tips 技術隨筆 有的沒的