一、簡介
控制台程序小巧、便捷,開發起來簡單。一般,我寫項目時習慣在原定客戶端之外,寫一個控制台的客戶端。這樣有幾個好處:
(1)開發量較Web或GUI少得多。
(2)運行起來簡單,占有資源很少。
(3)便於跟蹤程序的運行。比如,用 log4net 記錄日志的話,將appender-ref設置成ConsoleAppender,可以清楚看清系統運行軌跡,在使用nhibernate/activerecord開發時尤其方便。
(4)當為同一個系統開發兩種不同的UI時,會自覺的做好分層,這樣可以使系統的層次結構更清晰,便於維護。
然而,雖然控制台程序的開發量少,也還是有一些常用功能實現起來較繁瑣。比如,以下幾個問題:
(1)輸入密碼。用戶輸入密碼時,控制台顯示****而不是密碼明文;
(2)指令的解析與分派。控制台中,經常需要向程序輸入純字符串格式的指令,解析指令,解析參數的個數,調用相應的方法。
(3)指令的幫助系統。顯示全部指令及其介紹。
(4)指令的自動補全。
這幾個問題在寫控制台程序上經常會碰到,為此我寫了兩個類 ConsoleUtil 和 CmdDispatcher,實現了上述功能,以供復用。於此下載代碼。
代碼是C#3.0 寫的,若要用在其它C#版本,需要做一定的改動。
二、使用方式:
(1)輸入密碼
調用靜態方法String ConsoleUtil.ReadPassword(String msg, String errMsgOnNull) 獲取輸入的密碼。
(2)指令的解析、分派、自動補全與幫助系統
(a)創建一個 CmdDispatcher 對象。
(b)使用CmdDispatcher對象的AddCmdFunc方法,加入指令委托。這裡定義了五種委托:
delegate void Func0(); delegate void Func1(String s1); delegate void Func2(String s1, String s2); delegate void Func3(String s1, String s2, String s3); delegate void Func4(String s1, String s2, String s3, String s4);
AddCmdFunc方法有兩種使用方式。
AddCmdFunc(String cmd, Func0|Func1|Func2|Func3|Func4 func)
和
AddCmdFunc(String cmd, String argsString, String introduce, Func0|Func1|Func2|Func3|Func4 func)
後一種方式中 argsString 是該指令的參數字符串,introduce 是對這個指令的介紹。這兩個變量的唯一意義是顯示在該指令的help信息之中。如果使用前一種方式,該指令的help信息便是光禿禿的。
比如,
CmdDispatcher cd = new CmdDispatcher(); cd.AddCmdFunc("help", "無參數", "查詢幫助.", () => { cd.PrintHelp(); }); cd.AddCmdFunc("cmd1", "無參數", "指令cmd1.", () => { Console.WriteLine(String.Format("Invoke cmd1.")); }); cd.AddCmdFunc("cmd2", "arg0 arg1", "指令cmd1.", (arg0, arg1) => { Console.WriteLine(String.Format("Invoke cmd1({0},{1}).", arg0, arg1)); }); cd.AddCmdFunc("cmd3", (arg0, arg1, arg2) => { Console.WriteLine(String.Format("Invoke cmd1({0},{1},{2}).", arg0, arg1, arg2)); });
顯示出來的 help 信息為:
help 無參數
查詢幫助.
cmd2 arg0 arg1
指令cmd1.
cmd3 無參數
(3)通過CmdDispatcher對象的String ReadlineWithIntelliSence()方法獲取控制台輸入的指令.通過CmdDispatcher對象的Handle(String input)方法便可解析指令,分派給相應的委托完成。
舉例:
while (true) { Console.Write(cd.Prefix); // 在控制台上輸出提示符 >>。 String input = cd.ReadlineWithIntelliSence(); cd.Handle(input); }
(4)不匹配的指令的處理方法
CmdDispatcher有一個屬性,public Func0 DefaultFunc { get; set; } 。當CmdDispatcher 找不到匹配的委托時,便調用這個delegate。你可以自行設置 DefaultFunc,否則則用默認的內置 delegate。
三、一個完整的例子
下面是一個完整的例子:
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Text; 5 6namespace ConsoleTest 7{ 8 class Program 9 { 10 static Boolean EXIT = false; 11 static void Main(string[] args) 12 { 13 String id = ConsoleUtil.Readline("請輸入帳號:","帳號不能為空."); 14 String pwd = ConsoleUtil.ReadPassword("請輸入密碼:","密碼不能為空."); 15 Console.WriteLine("歡迎你,"+ id + "!"); 16 CmdDispatcher cd = CreateDispatcher(); 17 while (true) 18 { 19 Console.Write(cd.Prefix); 20 String input = cd.ReadlineWithIntelliSence(); 21 cd.Handle(input); 22 if (EXIT) return; 23 } 24 } 25 26 static CmdDispatcher CreateDispatcher() 27 { 28 CmdDispatcher cd = new CmdDispatcher(); 29 cd.AddCmdFunc("help", "無參數", "查詢幫助.", 30 () => { cd.PrintHelp(); }); 31 cd.AddCmdFunc("help", "cmd", "查詢指定指令的幫助.", 32 (cmd) => { cd.PrintHelp(cmd); }); 33 cd.AddCmdFunc("exit","無參數","退出程序.", 34 () => { EXIT = true; }); 35 cd.AddCmdFunc("cmd1", "無參數", "指令cmd1.", 36 () => { Console.WriteLine(String.Format("Invoke cmd1.")); }); 37 cd.AddCmdFunc("cmd1", "arg0", "指令cmd1.", 38 (arg0) => { Console.WriteLine(String.Format("Invoke cmd1({0}).", arg0)); }); 39 cd.AddCmdFunc("cmd2", "arg0 arg1", "指令cmd1.", 40 (arg0, arg1) => { Console.WriteLine(String.Format("Invoke cmd1({0},{1}).", arg0, arg1)); }); 41 cd.AddCmdFunc("cmd3", 42 (arg0, arg1, arg2) => { Console.WriteLine(String.Format("Invoke cmd1({0},{1},{2}).", arg0, arg1, arg2)); }); 43 return cd; 44 } 45 } 46} 47
運行結果:
本文配套源碼