December 2009 - 文章

Action<T> and Func<T>

在前一篇談Closure的文章裡,提到了delegate匿名方法,今天再補充一下delegate的簡化寫法。

在上回的程式範例中,為了要呼叫執行期間才產生的方法,我們宣告了delegate void MyFunc(string paramStr)。由於參數型別不同就要有獨立的宣告,若程式碼涉及多種參數各不相同的匿名方法,我們勢必要寫出一堆必要但是沒啥意義的delegate宣告。delegate宣告主要是要明確定義呼叫時傳入的參數及傳回值型別,當作方法變數的型別(即MyFunc[] funcs = new MyFunc[4];),常常只用一次就丟,每次要為這些"抛棄式"delegate宣告取名稱都害我要想半天,因此我始終覺得它是件該被省略的無聊事。

Action Delegate的出現解決了以上困擾! 從C# 2.0起,.NET Framework內建了Action, Action<T>, Action<T, T>, Action<T, T, T>, Action<T, T, T, T>,可以省下0到4個參數的delegate宣告。回到Closure文章的例子看一下實際應用,引用Action<T>,我們可省去MyFunc delegate的宣告,然後把funcs變數宣告那一列改成Action<string>[] funcs = new Action<string>[4];,其餘的程式碼完全不變,這樣就不用再花時間去想MyFunc這種鳥名字了。

Action適用於不傳回值的方法,若方法有傳回值,則.NET 2.0另外備有Func<TResult>, Func<T, TResult>, Func<T, T, TResult>, Func<T, T, T, TResult>, Func<T, T, T, T, TResult>可資利用。

以下補上簡單範例,希望讓新手朋友能對Action與Func有多一份認識。

using System;
using System.Text;
 
class Program
{
    static void Main(string[] args)
    {
        Action<int> f1 = i => { Console.WriteLine("i={0}", i); };
        f1(10);
        Action<int, string> f2 =
            (i, s) => { Console.WriteLine("i={0} s={1}", i, s); };
        f2(99, "Hello");
        Action<byte[], Encoding> f3 =
            (b, e) => { Console.WriteLine(e.GetString(b)); };
        f3(new byte[] { 65, 66, 67 }, Encoding.ASCII);
        Func<int, string> f4 =
            i => string.Format("i={0} i+1={1}", i, i+1);
        Console.WriteLine(f4(1));
        Console.Read();
    }
}

執行結果:

i=10
i=99 s=Hello
ABC
i=1 i+1=2

Closure in C#

今年因為jQuery的關係,對Javascript有較深入的研究(終於...),也認識了好用的Closure概念。

動態建立一個函數時把特定變數獨立保存一份,在特定場合裡是很犀利簡潔的解法,因此在進階Javascript程式開發中,Closure出現的機率還蠻高的。那麼,.NET, C#呢? 也可以做到Closure嗎?

在C#世界裡,對應Javascript的var myFunc = function(s) { alert(s); }這類變數結合匿名函數的概念,一般是用delegate加上匿名方法解決。

在C#裡要實踐Closure也很簡單,在匿名方法中直接呼叫外部的變數就可以了。若該變數屬區域性(Local),你會發現匿名方法在建立時會為這些區域變數建立一個複本供自己專用;反之,若變數並非區域性的,則會是多個匿名方法共用。

我試著用以下程式碼來示範。透過迴圈我產生了四個匿名方法放入funcs陣列中,匿名方法裡則大方地呼叫了intVar及outVar,其中intVar的存在範圍只限於for迴圈內,符合區域變數的定義。而在匿名方法中,每呼叫一次就增加一次intVar的值,依前段說明,funcs[0]會有自己的intVar,初始值為0;funcs[1]也有自己的intVar,初始值為1。而outVar宣告於for迴圈之外,funcs[0 – 4]共用一個,一修改數值,四個匿名方法都會被影響。

using System;
 
class Program
{
    delegate void MyFunc(string paramStr);
 
    static void Main(string[] args)
    {
        MyFunc[] funcs = new MyFunc[4];
 
        int outVar = 5;
        for (int i = 0; i < 4; i++)
        {
            int intVar = i;
            funcs[i] = (s) =>
            {
                Console.WriteLine(
                    "outVar = {0} intVar = {1} paramStr = {2}",
                    outVar, intVar, s);
                intVar++;
            };
        }
        funcs[0]("First Call");
        funcs[0]("Second Call");
        funcs[2]("Test1");
        funcs[3]("Test2");
        outVar = 6;
        funcs[2]("Test3");
        funcs[3]("Test4");
        Console.Read();
    }
}

執行結果為:

outVar = 5 intVar = 0 paramStr = First Call
outVar = 5 intVar = 1 paramStr = Second Call
outVar = 5 intVar = 2 paramStr = Test1
outVar = 5 intVar = 3 paramStr = Test2
outVar = 6 intVar = 3 paramStr = Test3
outVar = 6 intVar = 4 paramStr = Test4

我們來試著解釋以上結果。在First Call裡,funcs[0]的intVar還是初始值0,在Console.WriteLine後intVar++,因此在Second Call時,intVar就變成1了。而Test1中,funcs[2]有自己獨立的intVar,不受剛才funcs[0]呼叫的影響,顯示出初始值2,Test2裡funcs[3]則顯示intVar初始值3。到目前為止所有funcs[*]的outVar都是5。接著我們改變outVar=6,在Test3, Test4裡再次呼叫funcs[2], funcs[3],一如預期分別得到遞增後的intVar--3與4,而outVar則顯示新值6。

很有趣吧! 這是怎麼做到的? 其實Compiler在背後做了一大堆工作,大顆汗小顆汗才實踐了這番效果。大致原理是Compiler自行宣告了一個內部使用的Class(假設叫ToToMoMoClass),並宣告了一個intVar作為Class Memeber,接著為每個匿名方法建立一個ToToMoMoClass Instance,由於有4個Instance,就實踐了每個匿名方法都有自己一份intVar的效果。以上只是概念化的解釋,實踐做法裡還有不少深奧細節,有興趣深入的話可以參考延伸閱讀中的文章,有較詳盡的說明。

【延伸閱讀】

閒聊: 名牌軟體 vs 鐵肝趕死隊 (兼Karma 100紀念)

今天在Plurk上噗了一則有趣話題,引來不少討論:

同樣是花費數億元,企業寧可採購名牌軟體,忍受一堆既有限制將就使用並逐年上繳保護費,也不會用同樣的錢籌組一個要錢不要命(但夠專業)的鐵肝趕死開發團隊,花一年研發出100%符合需求的系統。理由為何? 1)品牌信賴感 2)名牌虛榮感 3)較有利於卸責 4)以上皆是

我個人的看法是4),這麼龐大而複雜的決策,各種因素應該或多或少都會摻雜一些吧!?

Alvin給了一個好玩的角度--因人而異: User選1,CxO選2,IT選3。我想也有可能CxO選1(有些主管甚至相信廠商勝過自己的部屬,既然要負成敗,就選信得過的人,這點KEN也提到了),User選2(我們公司用的那個軟體是法國人寫的柳...)。不過我覺得IT會認同3倒是沒什麼爭議,我的噗友以IT域領為主,3獲得了13票,4(也包含3)有12票... 讓我想起那句經典名言: "Nobody gets fired for buying Cisco/IBM/SAP/Oracle/Microsoft...”,都選了第一品牌還有問題,咬我呀!

另外,不少人(小天, BillChung, laneser, AskaSu)提出了類似的疑慮: 鐵肝趕死隊的品質、忠誠、永續維護存在著未知數! 名牌軟體有廠商背書,就像去7-11買東西出了問題,多半預期會得到妥善的處理,商譽是大廠商的命脈,自然會不計代價地維持每一個客戶的滿意度。反之,在路邊攤吃麵吃到小強,頂多就是不收錢,老板大不了下回改推到兩條街外做生意了。細究起來,也算是對於品牌的信賴感差別。

我猜想,名牌虛榮感確實存在!

兩個CIO在研討會中相遇,坐在一起聊天:

A: 你們公司會計系統用哪套?
B: 就SAP呀! 貴死了,又難用的要命... (嫌到極點但臉上盡是得意),咦? 那你們呢?
B: 哦! 我們公司自己開發的,User的需求很難搞,與其要花大工客製不如量身打...(被A打斷)
A: 哦,這樣哦... 嘿! 今天這會場設備挺不賴的。
B: OOXX@@##

畢竟口中不經意冒出"限量版柏金包"六個字再搭配輕微的嫌棄,絕對比"花90萬請老師傅依我的需求純手工製作"來得吭鏘有力,洋溢出擋不住的時尚,貴氣直衝雲霄,上流到令人銷魂呀~~~

最後再補一點,jonson提到名牌軟體象徵著Best Practice! 能做到跟軟體天人合一,也代表這家公司的制度流程已符合國際標準。理論上,IT人員應該可以"挾天子以令諸侯",要求User配合軟體的Best Practice設計改變作業流程與操作方式,擋掉一些客製需求;只是實務上,代誌嘸熊拱郎凶A哈泥甘單! 評估之後,改程式總是比改腦袋簡單,結局演變成在明明不建議的客製的地方被要求客製,在套裝軟體的By Desgin縫隙間求生存...

即便碎念了這麼多,還是不得不承認,名牌軟體之所以能享譽全球,一定有硬底子真功夫,產品本身是累積了許多實務經驗才修練出的最佳解,能歷經全世界一堆凱子大公司的考驗,軟體品質與設計周詳度自然不在話下,於是,系統裡每個細節裡都可能蘊含學問。身為開發者,若有幸參與這類專案(畢竟不是每家公司都買得起昂貴的玩具),千萬別入寶山空手而回,那就真的可惜囉。

閒聊完了,順便宣告,"我的Plurk Karma 100達成囉!!" 有一點一定要說嘴一下: Plurk至今,我沒發過踩線噗保Karma、也不曾外掛機器人發噗/推噗,算是一字一句紮實地爬上山頂的(雖然裡面有不少是垃圾話,但至少有花了心思想,其情可憫)。只可惜前兩天Plurk莫名其妙送了2點當做耶誕禮,害我莫名其妙坐了雲霄飛車,提早個把個月攻頂,不能算自力完成,可惜囉! (這讓我想到2012明明打算要在海面迫降,卻忽然到了西藏的情節)

筆記-物件繼承的virtual與new

來個物件繼承觀念及C#語法小考,試著用大腦編譯及執行以下程式,並預測執行結果:

using System;
 
class Program
{
    static void Main(string[] args)
    {
        ChildClass c = new ChildClass();
        ParentClass p = c as ParentClass;
        Console.WriteLine("===ChildClass===");
        c.MethodA(); c.MethodB(); c.MethodC();
        Console.WriteLine("===ChildClass as ParentClass===");
        p.MethodA(); p.MethodB(); p.MethodC();
        Console.Read();
    }
}
 
class ParentClass
{
    public virtual void MethodA()
    {
        Console.WriteLine("ParentClass.MethodA");
    }
    public void MethodB()
    {
        Console.WriteLine("ParentClass.MethodB");
    }
    public void MethodC()
    {
        Console.WriteLine("ParentClass.MethodC");
        MethodA(); MethodB();
    }
}
class ChildClass : ParentClass
{
    public override void MethodA()
    {
        Console.WriteLine("ChildClass.MethodA");
    }
    public void MethodB()
    {
        Console.WriteLine("ChildClass.MethodB");
    }
}

以上的程式碼目的在示範virtual與否的區別,實際執行結果如下,你答對了嗎?

===ChildClass===
ChildClass.MethodA
ChildClass.MethodB
ParentClass.MethodC
ChildClass.MethodA
ParentClass.MethodB
===ChildClass as ParentClass===
ChildClass.MethodA
ParentClass.MethodB
ParentClass.MethodC
ChildClass.MethodA
ParentClass.MethodB

在程式碼中MethodA被宣告成virtual,所以不管是ChildClass或轉型成ParentClass、或是在ParentClass的MethodC裡被呼叫,都會以ChildClass override過的版本為準。而MethodB因為沒有宣告virtual/override,等同於ChildClass及ParentClass裡各有自己的版本,在轉型成ParentClass後再呼叫,執行的就是ParentClass的版本;而宣告在ParentClass的MethodC中所引用的,也永遠以ParentClass的版本為準。

實際上,這段程式在編輯時,Visual Studio會苦勸你回頭是岸(源於Compiler提出的警告),ChildClass裡應宣告成public new void MethodB()較為妥當,代表此MethodB與父層物件並無繼承關係,純粹只是名稱相同。加new後,語意較明確,有助於他人(或自己在日後)對程式碼的理解。

Warning: 'ChildClass.MethodB()' hides inherited member 'ParentClass.MethodB()'. Use the new keyword if hiding was intended.

再追加一點,若把ChildClass MethodA前的override拿掉,也等同於宣告一個同名但無繼承關係的Method,跟加new效果相同,Visual Studio一樣會提出警告。

Warning 'ChildClass.MethodA()' hides inherited member 'ParentClass.MethodA()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. 

【延伸閱讀】

CODE-將地址中的阿拉伯數字轉為中文大寫

前幾天同事討論到要將地址資料中的阿拉伯數字都轉成中文大寫(一二三四...),我想起了前些時候看到的Microsoft Visual Studio International Feature Pack 2.0就內建了數字轉中文大寫的功能,試作如下。

程式主要是用Regex去比對出數字(\d+)的部分,逐一換成中文大寫。而更換時我用算位置的方法而不直接用Replace,以免把"12弄123號"搞成"十二弄十二3號";也因為要算位置,加上每次更換完字串長度可能會改變,所以也不能直接用foreach (Match m in Regex.Matches(…))把所有數字挑出來一次處理,必須用while (Match.Success)個個擊破。[2009-12-23更新: 以下寫法不夠精簡,另外有三百"一"十少一的問題,請改參考下方的改良版]

using System;
using System.Text.RegularExpressions;
using Microsoft.International.Formatters;
using System.Globalization;
 
namespace TestChtNumber
{
    class Program
    {
        static void Main(string[] args)
        {
            string addr = "台北市中正區重慶南路1段122號";
            Match m;
            while ((m=Regex.Match(addr, "\\d+")).Success)
            {
                int n = int.Parse(m.Value);
                string t =
                    EastAsiaNumericFormatter.FormatWithCulture("Ln", n,
                    null, new CultureInfo("zh-TW"));
                //"L"-大寫,壹貳參... "Ln"-一二三... "Lc"-貨幣,同L
                addr = addr.Substring(0, m.Index) + t +
                       addr.Substring(m.Index + m.Value.Length);
            }
            Console.WriteLine(addr);
            Console.Read();
        }
    }
}

得到結果: 台北市中正區重慶南路一段一百二十二號

很好用吧!! 另外,元件也支援簡繁轉換的功能,是另一項天上掉下來的禮物,不要錯過了。

【延伸閱讀】

【更新2009/12/23】接獲線報,這元件有個Bug,"310"會被轉成"三百十",而我們口語會說"三百一十",好好的美人兒偏偏要長一顆痣,實在是... (氣) 晚點再來想要如何整容。

【更新2009/12/23】感謝Dino與Phoenix的補充,讓我這條老狗又學到新把戲了(MatchEvaluator是好物呀!),以下我想到為解決三百"一"十問題的修正版,歡迎大家批評指教!

using System;
using System.Text.RegularExpressions;
using Microsoft.International.Formatters;
using System.Globalization;
 
namespace TestChtNumber
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 2000; i++)
            {
                Console.Write(FormatChineseNumber(i, false) + " ");
            }
            Console.WriteLine(FormatChineseNumber(
                string.Format("中華民國{0}年{1}月{2}日",
                DateTime.Today.Year - 1911,
                DateTime.Today.Month,
                DateTime.Today.Day), false));
            Console.WriteLine(FormatChineseNumber(12.345M, false));
            Console.Read();
        }
 
        /// <summary>
        /// 將字串的數字部分轉為中文大寫
        /// </summary>
        /// <param name="s">原始字串</param>
        /// <param name="moneyChar">
        /// 是否使用金額大寫,true時使用"壹貳參肆...", false時則為"一二三四..."
        /// </param>
        /// <returns>轉換後字串</returns>
        static string FormatChineseNumber(string s, bool moneyChar)
        {
            return Regex.Replace(s, "\\d+", m =>
            {
                int n = int.Parse(m.Value);
                return FormatChineseNumber(n, moneyChar);
            });
        }
 
        /// <summary>
        /// 修正EastAsiaNumericFormatter.FormatWithCulture出現"三百十"之問題,
        /// 本函數會將其修正為三百一十的慣用寫法
        /// </summary>
        /// <param name="n">要轉換的數字</param>
        /// <param name="moneyChar">
        /// 是否使用金額大寫,true時使用"壹貳參肆...", false時則為"一二三四..."
        /// </param>
        /// <returns>轉為中文大寫的數字</returns>
        static string FormatChineseNumber(decimal n, bool moneyChar)
        {
            //"L"-大寫,壹貳參... "Ln"-一二三... "Lc"-貨幣,同L
            string t =
                EastAsiaNumericFormatter.FormatWithCulture(
                moneyChar ? "L" : "Ln", n,
                null, new CultureInfo("zh-TW"));
            string pattern = moneyChar ?
                    "[^壹貳參肆伍陸柒捌玖]拾" :
                    "[^一二三四五六七八九]十";
            string one = moneyChar ? "壹" : "一";
            return Regex.Replace(t, pattern, m =>
            {
                return m.Value.Substring(0, 1) + one +
                    m.Value.Substring(1);
            });
        }
    }
}
Try Catch Block是否會影響效能?

跟網友在部落格上討論效能時提到一個議題--在迴圈中加入try...catch是否會影響效能?

依我的認知,try...catch只有在發生Exception時才會嚴重危害效能,平時正常執行時,我們倒可以"幾乎忘了它的存在"。

不過,我過去似乎還真沒用測試驗證過這一點,既然聊到了,就順手寫幾行Code實測一番:

using System;
using System.Diagnostics;
 
namespace TryCatchCost
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            int RUN_COUNT = 200 * 10000;
            long sum = 0;
            bool flag10K = false;
            //Test 1, no try catch
            sw.Start();
            for (int i = 0; i < RUN_COUNT; i++)
            {
                sum += i;
                if (i == 10000) flag10K = true;
            }
            sw.Stop();
            Console.WriteLine("Test1 Sum={0:N0} Time={1:N0}ms",
                sum, sw.ElapsedMilliseconds);
            //Test 2, try catch
            sw.Reset(); sum = 0;
            sw.Start();
            for (int i = 0; i < RUN_COUNT; i++)
            {
                try
                {
                    sum += i;
                    if (i == 10000) flag10K = true;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Error: {0}", e.Message);
                }
            }
            sw.Stop();
            Console.WriteLine("Test2 Sum={0:N0} Time={1:N0}ms",
                sum, sw.ElapsedMilliseconds);
            //Test 3, try catch and one exception
            sw.Reset(); sum = 0;
            sw.Start();
            for (int i = 0; i < RUN_COUNT; i++)
            {
                try
                {
                    sum += i;
                    if (i == 10000)
                        throw new ApplicationException("10K");
                }
                catch (Exception e)
                {
                    Console.WriteLine("Error: {0}", e.Message);
                }
            }
            sw.Stop();
            Console.WriteLine("Test3 Sum={0:N0} Time={1:N0}ms",
                sum, sw.ElapsedMilliseconds);
 
            Console.Read();
        }
    }
}

Test1, 2, 3都分別執行200萬次的統計加總,Test2與Test1差別在於Test2中加入try catch,但完全不觸發catch段的流程;Test3則故意觸發一次Exception。為了公平起見,三個測試都加入一列if (i == 10000)。測試數據如下:

Test1 Sum=1,999,999,000,000 Time=17ms
Test2 Sum=1,999,999,000,000 Time=18ms
Error: 10K
Test3 Sum=1,999,999,000,000 Time=46ms

Test1與Test2的執行時間相同,應可推論加入try…catch不影響效能。而即使200萬次只觸發1次例外,Test3的執行時間比Test1, 2慢了一倍以上。算是驗證了推論---try…catch只有在發生Exception時才會影響效能,請安心使用

【黑暗水電工專欄】好用的省水閥及注意事項

好久沒有分享水電心得了。先前發表水龍頭漏水維修教學,創下IT部落格界的傳奇。這幾天剛好又學到一些水電經驗,照例要分享一下,才不負黑暗水電工的美譽。

首先介紹一個好物--省水閥

簡單來說,它是一個切換式開關,裝在水龍頭的出水口。安裝完成後,可將水龍頭打開,接著就變成由它來控制出水,頂一下打開,再頂一次關閉,就像jQuery.toggle()的效果一樣。(謎之聲: 這樣也要扯到Coding,你夠了沒?)

由於閥內有濾網結構,因此水流出時會有類似蓮蓬頭灑水的效果,讓水流量變小以達到節省用水的目標。(感覺上跟馬桶水箱放保特瓶的原理差不多)

實際使用半年,我覺得它的好處不少,主要還有以下幾點:

  1. 洗手時可用手指控制開關水,比操作水龍頭把手或轉動開關旋鈕來得省力。依吉爾勃斯的動作經濟原則,等同以第二級動作(手指+手腕)取代第四級動作(手指+手腕+前臂+上臂),有很大的工作效率改善。(詳見參考資料第29頁,沒想到唸了四年的工管,竟在此派上用場)
  2. 前述的動作改善,不但省力,也可大幅縮短完成關水操作的時間,節省了洗手完成到關水完成間的無效出水,省上加省。
  3. 洗手後不用再接觸水龍頭把手,減少再弄髒被污染的機率。而正確洗手步驟"濕、搓、沖、捧、擦"中的捧也可以省略,又再節省一些用水。
  4. 對原本搆不到水龍頭開關的小朋友來說,這是不得了的聖品。警告: 由於此方便性增加了小朋友玩水的機率,可能抵消部分的省水效果。

不過,省水閥也不是全然都沒有缺點。例如: 出水量變小,有時會沖洗力道略嫌不足,而以前蓋住部分出水口形成強力水柱沖掉頑垢的技巧也沒法再用了。但這兩天遇到的才是最大問題...

這幾天寒流來襲,熱水用得多,好死不死熱水器偏偏就壞了。開熱水時,水點火的功能完全失效,換了新電池亦然。年初才換新熱水,冬天居然無熱水可用,讓人有想"冰斗"的衝動。隔天叫了熱水器廠商來修,千里迢迢趕來的師傅檢查了電池正常,接著拆開熱水器外殼,要求開水測試時發現我們有裝省水閥,師傅才恍然大悟,開始碎唸: "啊! 果然又是這個,就在想說這台這麼新不可能會壞,就是你們這些人愛省錢,裝這種東西..."(碎念長達數分鐘,此處省略)。

原來,因為省水閥是從出水口止住水。若水龍頭被調成冷熱水都開啟,配合出水口被封閉,冷熱水管間就形成了通路,會造成水壓改變,影響了熱水流動觸發點火的機制。

心得如下:

  1. 使用省水閥應養成習慣,使用完畢請保持在純冷水的狀態,避免冷熱水串通,就不會造成無法點火的問題。
  2. 熱水器廠商的客服SOP應該要先針對無法點火的個案排除省水閥因素,透過電話解決,可以加快問題排除速度並省去非必要的人員派工成本。
  3. 本次前來的修理師傅回去應該可以發表KB或茶包射手系列文章。
  4. 其實,在產品包裝(如下圖)上及網頁上都有註明此一注意事項,但我想應該沒什麼人會詳讀吧? 出問題時也很少人會想到問題出在這兒。這是一個經典的RTFM案例,提供各位引以為鑑! orz

LINQ to XML vs XPath的簡單效能測試

如果你使用的平台是.NET 3.5,在操作XML文件時會有三種選擇: LINQ to XML, LINQ to XML with XPath以及傳統的XmlDocument。既然有三種選擇,排除個人主觀偏好,想知道哪一種做法的效能最好呢?

之前有個迷思,一直覺得LINQ表達方式友善,理論上會付出效能上的代價(正所謂有一好沒兩好)。所以有時針對複雜的元素查詢,我會using System.Xml.XPath,然後改用XPathSelectElements()查詢,直到無意間發現了一篇談LINQ to XML與XPath Benchmark的文章,才發現我錯了。該文作者試了一個120,000筆資料, 54MB大小的XML檔案,LINQ to XML用LINQ語法查詢,竟比叫用XPathSelectElements()快了五倍!!

我自己也做了一個測試,產生一個36M大小20,000 * 100個Node的XML文件,分別用LINQ to XML, XPathSelectElements及XmlDocument.SelectNodes去查詢同樣條件,統計Node數。依我的測試結果,也驗證了LINQ to XML確實比XPathSelectElements來得快,至於XmlDocument,我們就忘了它吧...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;
using System.Diagnostics;
using System.Xml.XPath;
using System.Xml;
 
namespace TestXmlPerformance
{
    class Program
    {
        static void GenSampleXml()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<root>");
            Random rnd = new Random();
            for (int i = 0; i < 20000; i++)
            {
                sb.AppendFormat("<pack id=\"{0}\">", i);
                for (int j = 0; j < 100; j++)
                    sb.AppendFormat("<item model=\"{0}\" />",
                        rnd.Next(20));
                sb.Append("</pack>");
            }
            sb.Append("</root>");
            File.WriteAllText(".\\Sample.xml", sb.ToString());
        }
 
        static void Main(string[] args)
        {
            //GenSampleXml();
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                XDocument xd = XDocument.Load(".\\Sample.xml");
                var q = from o in xd.Descendants("item")
                        where o.Attribute("model").Value == "7"
                        select o;
                Console.WriteLine(q.Count());
                sw.Stop();
                Console.WriteLine("Test 1: {0}ms", 
                    sw.ElapsedMilliseconds);
            }
 
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                XDocument xd = XDocument.Load(".\\Sample.xml");
                Console.WriteLine(
                    xd.XPathSelectElements("root/pack/item[@model='7']")
                    .Count()
                );
                sw.Stop();
                Console.WriteLine("Test 2: {0}ms", 
                    sw.ElapsedMilliseconds);
            }
 
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                XmlDocument xd = new XmlDocument();
                xd.Load(".\\Sample.xml");
                Console.WriteLine(
                    xd.DocumentElement
                    .SelectNodes("pack/item[@model='7']").Count
                );
                sw.Stop();
                Console.WriteLine("Test 3: {0}ms", 
                    sw.ElapsedMilliseconds);
            }
 
            Console.Read();
        }
    }
}

【 測試數據 】

100507
Test 1: 3268ms
100507
Test 2: 3960ms
100507
Test 3: 9246ms

【茶包射手筆記】Visual SourceSafe Check-In時彈出Invalid Handle錯誤(Vista/Windows 2008)

不知從何時開始,在我的Windows 2008上使用Visual SourceSafe Check-In檔案時(用VS2008 Check In亦然),每簽入一個檔案就固定要彈出一個"Invalid Handle"的警示訊息,雖然程式碼會順利被簽入,並不影響功能;但想像一下,一口氣簽入js或imgs目錄裡數十上百個檔案,要在枯坐電腦前按掉數十上百個"Invalid Handle" MessageBox感覺還真蠢。雖有所不便,但上回小小查過一次並無所獲,就鄉愿地隱忍至今...

最近好幾個系統趕著上線,Check Out/Check In的操作特別多,自我感覺特別愚蠢,尤其是急如星火之際還要多個無謂動作十分讓人光火。今天趁著專案上線空檔,又花了點時間再查一次,這回手氣很好,查到了有人在Vista + VP環境下遇到類似問題,裝了Visual SourceSafe 2005 Update解決。雖然我是Windows 2008 + LAN,抱著裝Update有病治病,沒病強身的心態,義無反顧安裝下去,薑薑薑薑... Check In會彈出Invlid Handle的問題就這麼消失了!

順道一提,這個Update修好了另一個要命的大問題: Check In/Out後PDF檔案會壞掉,建議有在用VSS2005的人都裝一下。

MEMO-ORACLE grant with grant option

【Scenario】
Oracle上有三個Schema, AAA, BBB與CCC。

AAA下有個tblMyData,BBB下打算建一個View vw2ndHandData: SELECT * FROM tblMyData WHERE Col1 = 'A',所以要Grant SELECT on AAA.tblMyData to BBB,有了tblMyData的查詢權限BBB才能順利將View建立起來。

此時我們要開放vw2ndHand供CCC查詢,於是用BBB身份下了Grant SELECT on vw2ndHand to CCC,會得到以下錯誤:

ORA-01720: grant option does not exist for 'AAA.tblMyData'

即便由AAA執行Grant SELECT on tblMyData to CCC也是一樣的結果。

【說明】
這是授權資料轉手的議題,原則上AAA把讀取權限放給BBB,不代表BBB可以將AAA資料再授權給其他人讀取! 當BBB試圖Grant SELECT vw2ndHandData to CCC,等於會讓CCC看到AAA.tblMyData,就會違反了資料不得轉賣的限制。
要解決這個問題,BBB要獲得"可轉賣"的授權,寫法是在grant時多加上with grant option:

Grant SELECT on tblMyData to BBB with grant option

醬就可以嚕!

【黑暗影評】2012(有雷慎入)

【警告: 本文涉及大量電影2012的情節,如果你還沒看過該片,不想破壞看電影的樂趣,請立刻點選瀏覽器右上角的X關閉本網頁】

為慶祝黑暗女王**(消音)大壽,特別舉辦本年度唯一的院線片欣賞會。難得進戲院看片,想當然爾要選氣勢磅薄的史詩巨作,才配得上戲院壯闊的銀幕、震憾的音響... 最後我們不免俗地挑了2012,把地球整個翻過來應該場面夠大了吧? 原本想配上IMAX衝一下視聽境界的極致,可惜2012尚無IMAX版可看,但我們還是挑了IMAX戲院,非假日配合信用卡優惠,一張票才160而已,比我想像的便宜多了,只比三個多小時的停車費多一點(這樣說來,停車費好貴)。

上班日的下午,戲院裡只有小貓兩三隻,600個座位大約只坐了約一成,一小撮人一起體驗地球毁滅的奧義。"天崩地裂噴岩漿,地震海嘯漫高山,八字超硬死不了,破鏡重圓終平安",這差不多就是整個故事的大綱。由於忘了關閉茶包射手模式,即便鄰座女生緊張到雙手緊握座位把手,整個人都弓起來的當下,我心中卻一直在"嘖嘖嘖,太扯了吧?" "這樣也行?" "不會吧?" "我就知道" orz... 搞得樂趣減半。綜觀以上事證,得到一個推論: "比扯鈴還扯"。

以下雜記一些心得(真糟,我怎麼幾乎都在挑剔不合理之處):

  • 有不少典型的電影元素: 男女主角婚姻失合、男主角由落魄漢變大英雄、歷險之後破鏡重圓、瘋瘋顛顛的人通常才是先知、小咖也能靠一席演講在30秒內改變一票大頭的價值觀... 因為太典型了,一猜就中,就少了劇情急轉直下的玄疑感,略損好看度。
  • 地面永遠都裂在剛好車子後方一公分處,無論如何樓塌車墜,前方永遠剛好有留下夠一台車子穿過的道路空間,飛機在兩棟倒下的高樓夾擊下也不會因崩塌掉落物受損... 主角的好運程度我估計可以連中兩千次樂透頭彩。
  • 一般地面、建物裂縫多因搖晃後產生,但片中常常沒什麼震動就牆面或地表就開始龜裂,比較像皮膚缺乏滋潤強裂的感覺。我建議應該要加強保濕,洗過澡記得要擦一下Body Lotion。
  • 只上過幾個星期的飛機駕駛課程,就能操控飛機完成各種特技動作,此人應是萬中選一、百世難尋的飛航奇才。
  • 第一次發現原來飛機可以在路邊靠"九五加滿"補充燃料,我還以為航空燃油跟汽油差很多哩!
  • 數十萬人知悉的重大秘密居然能守住兩年? 到底有沒有把水果報的狗仔記者放在眼裡?(氣)
  • 任何意圖洩漏秘密的人馬上會遭逢"不測",那麼搞地下電台推廣末日預言的吳樂天為什麼沒被抓去做消波塊?
  • 當男主角與妻子新歡男友逐漸建立友情,眼看災難結束後可能衍生3P問題,新男友就忽然莫名其妙死掉了,編劇迴避難題的手法太偷懶了啦? (讓他愛上俄國佬情婦原本也是一種解法,不過也沒什麼創意就是了,所以乾脆讓兩個都死掉)
  • 激起人類的憐憫心,讓所有人都上船無論如何是破壞原計劃的冒險之舉,最後若糧食空間資源不足危及生存,或是艙門最後沒能關上撞爛一艘船害死更多人,大家還是覺得年輕科學家基於感性而做出的決定是對的嗎?
  • 西藏人好厲害,連老阿媽講英語嘛也通。
  • 性格俄國人的飛機驚險地在懸崖邊停住時,臉上實在不該露出笑意! 一般來說,化險為夷之後馬上露出笑容的人註定要掛點,他可能太少看電影了,這是老梗。
  • 整個國家幾乎都天崩地裂了,尚未淪陷地區卻多半還有電有燈能通電話,有可能這樣較能看清楚災難發生時的細節吧?
  • 艘門沒關上就不能發動引擎? 把程式叫出來改一下不就好了。什麼? 這專案沒簽保固,而且沒附Source Code?
  • 說實在話,我認為年輕科學家的上司只是非常堅持要依計劃執行任務以完成人類存續的偉大目標,要刻意解釋成自私冷血似乎也有失公允。基於自己濃厚的工程師性格,我並不贊同電影中把他歸為反派。
  • 依理而論,當世界都要毁滅了,各國會開誠佈公攜手合作的情節,我認為有失合理性。一切應只會建立在交相賊的基礎上,實力強的講話大聲,不致是各國平起平坐。像中國擁有全球最高海拔及及無人能及的勞動人力兩項優勢,感覺分到的資源太少了。

雖然有以上諸多不合理處,憑良心講,這部片的災難特效做得很用心,十分酷炫,還是值得一看的"場面片",以上報告。

CODE-透過程式修改App.config

小小的範例程式。

手上有個排程定期執行的程式(為了做到執行時不顯示Console視窗,我選擇做成Window Form專案,再讓Form1隱形[補充2009-12-04: 此處用Form1是因為我還是寫了一個方便開發測試Debbug專用的UI,事實上連Form1可以不用建立,直接執行必要的邏輯即可,謝謝],設定都放在MyApp.exe.config的AppSettings裡。但其中有些設定值較複雜,需要注意是否符合規則、或是要加密後儲存。由於不想另外寫UI或獨立設定程式,於是我決定用"MyApp.exe set configKey configValue"命令列執行方式將設定值檢核及加密邏輯內建在同一支執行檔裡。

以下就是我的範例,供大家參考指教。(主要是借重Configuration.Save Method將修改值寫回)

[STAThread]
static void Main(string[] args)
{
    if (args.Length == 3)
    {
        string mode = args[0];
        string configKey = args[1];
        string configValue = args[2];
        if (mode == "set")
        {
            System.Configuration.Configuration conf =
                ConfigurationManager.OpenExeConfiguration(
                    ConfigurationUserLevel.None);
            //... Logic of conversion or checking on configValue ...
            if (conf.AppSettings.Settings[configKey] == null)
                conf.AppSettings.Settings.Add(configKey, configValue);
            else
                conf.AppSettings.Settings[configKey].Value = configValue;
            conf.Save(ConfigurationSaveMode.Modified);
        }
        MessageBox.Show("Config [" + configKey + "] is set!");
        return;
    }
 
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
}
MEMO-在Oracle裡查看物件被誰鎖定

試圖變更Procedure內部使用的暫存資料表:

alter table TEMPBUFFER modify EXCHANGERATE NUMBER(14,5)

執行時一直傳回 ORA-14450 attempt to access a transactional temp table already in use

理論上Procedure不會一直在執行中,應該是有人手動操作時沒有Commit,到底是誰呢?

DBA指點我用以下方法,可以查出誰在鎖定它: (要被Grant Permission才可用)

select * from v$session
where SID = (
      select SID from v$lock where id1=(
             select object_id
             from user_objects
             where object_name=UPPER('TEMPBUFFER')
      )
)

搜尋

Go

<December 2009>
SunMonTueWedThuFriSat
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789
 
RSS
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication