小工具?不,這是小工具的集合!

ok,好像好久好久沒有寫博客了,git

emmmm,一個是沒時間,另外一個,應該是感受本身知道的太少了吧,不敢寫了程序員

本來是打算寫一個系列,結果發現不少地方本身都是有些不夠的,因此就一直放着了,數據庫

此次趁着國慶,補上一篇吧,算是一個小工具的實例,文末會提供源碼下載工具

 

就是不知道大佬們有沒有遇到過這種狀況啊,spa

可能有時候要批量處理一些東西,多是文件,多是數據,總之就是處理量很是大,pwa

正常人吧,要嘛是分發給不少人處理,要嘛就是一我的哭唧唧的弄上好幾天,日誌

容易出現錯漏不說,一旦要求改了,ok,從新來過吧,code

咱們程序員就不同了,不會偷懶的程序員不是一個好的死肥宅,orm

噼裏啪啦寫好一個小工具,而後就能夠喝茶了,xml

待處理的文件或者數據,不管是一千仍是一萬,對咱們來講只是一個數字而已,

哪怕你要求改了,ok,我工具改一下,一樣能夠施施然的跑去休息。

 

我應爲工做緣由,常常寫一些工具代碼,

看同事寫工具代碼的話,他們通常都是新建一個控制檯程序,

而後要麼寫類,要麼寫方法,在Main中調用就好,

這個時候,問題就來了:

可能一個Main裏面,全是被註釋的其餘工具調用代碼,時間長了誰也不知道這些是幹啥的,

用方法去區分工具的話,一個工具每每可能衍生出多個方法,調用的尋找都極不方便,

新建控制檯項目的話,花銷太大,不少方法能夠重複使用,複製來複制去也是至關難以管理,

用類區分卻是不錯,不過,調用起來也是麻煩,得先去Main中註釋掉其餘工具,只留下待執行的工具,

若是要執行多個工具,還得停下來去修改Main,體驗感一樣極不友好,

好比說這樣,上面的代碼全是之前寫的工具,真正要執行的是81行的方法,

能夠想象,久而久之,這裏誰看到了都要頭疼,

 

爲了方便本身,因此抽空作了一個小項目,用於管理這些小工具,看圖說話,

 

 

先說說思路吧,卻是蠻簡單,就是反射,

先定義一個父類BaseFun,全部封裝的小工具類都繼承它,

1         public class TestClass : BaseFun
2         {
3 
4         }

另外有三個參數公用

 1         /// <summary>
 2         /// 獲取當前程序集
 3         /// </summary>
 4         Assembly _assembly = Assembly.GetExecutingAssembly();
 5 
 6         /// <summary>
 7         /// 當前選擇的類,此處不該這樣寫,僅做參考
 8         /// </summary>
 9         Type selType = _assembly.GetType(cmb_Class.SelectedValue.ToString());
10 
11         /// <summary>
12         /// 當前選擇的方法,此處不該這樣寫,僅做參考
13         /// </summary>
14         MethodInfo selMethod = selType.GetMethod(cmb_Fun.SelectedValue.ToString());

而後經過反射找到全部父類是BaseFun的類,加載至第一個下拉框裏面,

這裏簡單用到了反射和委託,不熟的童鞋能夠多瞅幾遍,大佬勿噴,

 1         /// <summary>
 2         /// 窗體加載時執行
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private void F_Main_Load(object sender, EventArgs e)
 7         {
11             // 綁定類的信息
12             BindCom(
13                 cmb_Class,// 待綁定的下拉框
14                 _assembly.GetTypes(),// 獲取程序集中全部的類
15                 c => c.BaseType == typeof(BaseFun),// 父類是BaseFun
16                 c => new ComBoxItem() { Display = c.Name, Value = c.FullName });
17         }
18 
19         /// <summary>
20         /// 綁定下拉框選項
21         /// </summary>
22         /// <typeparam name="T"></typeparam>
23         /// <param name="cmb">待綁定的下拉框</param>
24         /// <param name="dataList">數據集</param>
25         /// <param name="funcWhere">過濾條件,委託</param>
26         /// <param name="func">返回下拉項,委託</param>
27         public void BindCom<T>(ComboBox cmb, ICollection<T> dataList, Func<T, bool> funcWhere, Func<T, ComBoxItem> func)
28         {
29             List<ComBoxItem> list = new List<ComBoxItem>();
30 
31             if (!dataList.HasItems()) return;
32 
33             // 循環數據集
34             foreach (var item in dataList)
35             {
36                 // 執行條件
37                 if (funcWhere.Invoke(item))
38                     list.Add(func.Invoke(item));
39             }
40 
41             if (!list.HasItems()) return;
42 
43             // 綁定數據集
44             ComBoxItem option = list[0];
45             cmb.ValueMember = nameof(option.Value);
46             cmb.DisplayMember = nameof(option.Display);
47             cmb.DataSource = list;
48         }
49 
50     /// <summary>
51     /// 下拉框選項
52     /// </summary>
53     public class ComBoxItem
54     {
55         /// <summary>
56         ///57         /// </summary>
58         public string Value { get; set; }
59         /// <summary>
60         /// 文本
61         /// </summary>
62         public string Display { get; set; }
63     }

而後就是去找每一個類裏面的方法了,這裏我考慮到可能會要求彈框提示一下運行結束,或者展現一些運行信息什麼的,

因此就很果斷的限定了返回值,只有當返回值爲Result類型的時候,它纔會去綁定到第二個下拉框中,話說這個Result類的命名好像不太好,嘖,再說吧

 1         /// <summary>
 2         /// 選項更改時執行,從新綁定方法列表
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private void cmb_Class_SelectedIndexChanged(object sender, EventArgs e)
 7         { 9             // 獲取當前選擇的類
10             selType = _assembly.GetType(cmb_Class.SelectedValue.ToString());
11 
12             if (selType == null) return;
13 
14             // 綁定方法的信息
15             BindCom(
16                 cmb_Fun,
17                 selType.GetMethods(),// 返回類中全部公開方法
18                 c => c.ReturnType == typeof(Result) && c.DeclaringType == selType,// 返回類型爲Result,且是由當前類定義,而不是繼承自父類的方法
19                 c => new ComBoxItem() { Display = c.Name, Value = c.Name });
20         }
21     /// <summary>
22     /// 返回結果
23     /// </summary>
24     public class Result
25     {
26         /// <summary>
27         /// 消息
28         /// </summary>
29         public string Msg { get; set; }
30 
31         /// <summary>
32         /// 運行時間,ms
33         /// </summary>
34         public long RunTime { get; set; }
35 
36     }

找到了方法以後,就應該開始綁定參數了,

 1         /// <summary>
 2         /// 選項更改時執行,從新綁定參數列表
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private void cmb_Fun_SelectedIndexChanged(object sender, EventArgs e)
 7         {
 8             selMethod = selType.GetMethod(cmb_Fun.SelectedValue.ToString());
 9             BindPara();
10         }
11         /// <summary>
12         /// 綁定參數列表
13         /// </summary>
14         private void BindPara()
15         {
16             // 清空全部控件
17             flp_Para.Controls.Clear();
18 
19             string name = $"M:{selType.FullName}.{selMethod.Name}";
20 
21             int y = 5;
22 
23             if (selMethod.GetParameters().Length > 0)// 拼接尋找方法註釋的name屬性值
24                 name += $"({string.Join(",", selMethod.GetParameters().Select(c => c.ParameterType.FullName))})";
25 
26             // 循環方法所需的全部參數
27             foreach (var item in selMethod.GetParameters())
28             {
29                 int x = 0;
30 
31                 // 加載參數
32                 SkinLabel paraName = new SkinLabel
33                 {
34                     Location = new Point(x, y + 2),
35                     TextAlign = ContentAlignment.MiddleRight,
36                     Size = new Size(80, 20),
37                     Text = item.Name + ""
38                 };
39                 paraName.MouseMove += Form_MouseDown;
40 
41                 x += paraName.Size.Width + 5;
42 
43                 // 加載文本框
44                 SkinTextBox text = new SkinTextBox
45                 {
46                     Name = item.Name,
47                     Size = new Size(150, 20),
48                     Location = new Point(x, y),
49                     WaterText = GetNote(name, item.Name)// 添加水印註釋
50                 };
51 
52                 x += text.Size.Width + 5;
53 
54                 // 加載參數類型
55                 SkinLabel paraType = new SkinLabel
56                 {
57                     Location = new Point(x, y + 2),
58                     TextAlign = ContentAlignment.MiddleLeft,
59                     Size = new Size(70, 20),
60                     Text = item.ParameterType.Name
61                 };
62                 paraType.MouseMove += Form_MouseDown;
63 
64                 y += 27;
65 
66                 flp_Para.Controls.Add(paraName);
67                 flp_Para.Controls.Add(text);
68                 flp_Para.Controls.Add(paraType);
69             }
70         }

考慮到可讀性,因此我把參數的註釋也找了出來,綁定到文本框的水印中去了,

Winform自帶的文本框是沒有水印這個功能的,因此我用了第三方的水印控件CSkin,

那麼,這時候確定有人問了,C#代碼的註釋怎麼整,

代碼編譯後的Dll裏面是沒有註釋的,因此反射也找不到註釋,總不能去讀.cs文件吧,

其實簡單設置一下,VS就會自動幫咱們生成一份註釋文檔,

最後在bin\Debug目錄下,就會有一個XML註釋文檔,咱們直接讀取它就能夠了,全部類和方法的節點都是member,寫好尋找代碼就能夠了

        /// <summary>
        /// 返回註釋信息
        /// </summary>
        /// <param name="name">名稱</param>
        /// <param name="para">參數</param>
        /// <returns></returns>
        private string GetNote(string name, string para)
        {
            // 讀取XML
            XDocument document = XDocument.Load(_assembly.GetName().Name + ".xml");

            // 根據name尋找節點
            var item = document.Descendants("member").Where(c => c.Attribute("name").Value == name).FirstOrDefault();

            if (item == null) return "";

            // 若參數名稱爲空
            if (string.IsNullOrWhiteSpace(para))
                return (item.Element("summary")?.Value + "").Replace("\n", "").Trim();

            // 返回參數註釋
            return (item.Elements("param").Where(c => c.Attribute("name").Value == para).FirstOrDefault()?.Value + "").Replace("\n", "").Trim();
        }

到這基本方法都能找對了,參數也能加載出來,接下來就是執行方法了,

 1         /// <summary>
 2         /// 按鈕單擊時執行,執行選中的指定方法
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private void bt_Exec_Click(object sender, EventArgs e)
 7         {
 8             List<object> list = new List<object>();
 9 
10             // 循環方法的全部參數
11             foreach (var item in selMethod.GetParameters())
12             {
13                 // 尋找和參數名稱相同的控件
14                 Control con = flp_Para.Controls.Find(item.Name, false).FirstOrDefault();
15                 object obj = null;
16 
17                 #region 參數校驗
18 
19                 if (con == null)
20                 {
21                     MessageBox.Show($"缺乏參數:{item.Name}");
22                     return;
23                 }
24                 if (string.IsNullOrWhiteSpace(con.Text))
25                 {
26                     MessageBox.Show($"{item.Name}:值爲空");
27                     return;
28                 }
29 
30                 try
31                 {
32                     obj = Convert.ChangeType(con.Text, item.ParameterType);
33                 }
34                 catch (Exception)
35                 {
36                     MessageBox.Show($"{item.Name}:類型錯誤 ({item.ParameterType.Name})");
37                     return;
38                 }
39 
40                 #endregion
41 
42                 list.Add(obj);
43 
44             }
45 
46             Result res = (Result)selMethod.Invoke(_assembly.CreateInstance(selType.FullName), list.ToArray());
47 
48             if (cb_Log.Checked)
49             {
50                 res.Msg += $"\n{selType.Name}\t{selMethod.Name}\t運行時間:{res.RunTime} ms\n";
51                 MessageBox.Show(res.Msg);
52             }
53         }

最後要注意的是寫這些工具方法入口的規則,只要返回值爲Resule,就能找到,加載,而後運行,

但同時我也提供了一個更好的入口,

內部更多的實現就不展現了,我會提供源碼下載地址,大概思路即是如此,

        public Result Fun1()
        {
            // 推薦寫法,自動計算方法運行時間,自動拼裝日誌路徑,自動記錄每一次的執行
            // logPath:日誌文件路徑
            return RunFun((logPath) =>
            {
                
                // 寫入日誌文件
                base.WriteLog(logPath, "lalal");

                // 方法運行結束後,在彈出的對話框中展現
                Res.Msg += logPath;
                return Res;
            });
        }


        /// <summary>
        /// 運行
        /// </summary>
        /// <param name="func"></param>
        /// <returns></returns>
        public Result RunFun(Func<string, Result> func)
        {
            Res = new Result();
            // 拼裝日誌文件路徑
            string logPath = LogStarPath + GetMethodName(2) + ".log";

            WriteLog(logPath, "==========Star==========");

            // 定時器
            Stopwatch watch = new Stopwatch();
            // 開始計時
            watch.Start();
            // 執行方法
            func.Invoke(logPath);
            // 中止計時
            watch.Stop();
            // 返回運行時間
            Res.RunTime = watch.ElapsedMilliseconds;

            WriteLog(logPath, "==========End ==========\t" + Res.RunTime + " ms\n");
            return Res;
        }

 

還有不少想作的啊,

好比說默認值這個玩意兒我不知道應該如何綁定到文本框裏,

如今還沒作連接數據庫,

我還想記錄每一次運行的參數,弄個下拉框,選一下就能夠直接綁定一塊兒曾經輸入過的參數,這樣也是很方便的,

之後再慢慢加吧,歡迎大佬們指出不足之處,

 

 碼雲地址:https://gitee.com/StepDest/FunctionAction

相關文章
相關標籤/搜索