zoukankan      html  css  js  c++  java
  • 小工具?不,这是小工具的集合!

    ok,好像很久很久没有写博客了,

    emmmm,一个是没时间,另一个,应该是感觉自己知道的太少了吧,不敢写了

    原本是打算写一个系列,结果发现很多地方自己都是有些不够的,所以就一直放着了,

    这次趁着国庆,补上一篇吧,算是一个小工具的实例,文末会提供源码下载

    就是不知道大佬们有没有遇到过这种情况啊,

    可能有时候要批量处理一些东西,可能是文件,可能是数据,总之就是处理量非常大,

    正常人吧,要嘛是分发给很多人处理,要嘛就是一个人哭唧唧的弄上好几天,

    容易出现错漏不说,一旦要求改了,ok,重新来过吧,

    我们程序员就不一样了,不会偷懒的程序员不是一个好的死肥宅,

    噼里啪啦写好一个小工具,然后就可以喝茶了,

    待处理的文件或者数据,无论是一千还是一万,对我们来说只是一个数字而已,

    哪怕你要求改了,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就会自动帮我们生成一份注释文档,

    最后在binDebug目录下,就会有一个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("
    ", "").Trim();
    
                // 返回参数注释
                return (item.Elements("param").Where(c => c.Attribute("name").Value == para).FirstOrDefault()?.Value + "").Replace("
    ", "").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 += $"
    {selType.Name}	{selMethod.Name}	运行时间:{res.RunTime} ms
    ";
    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 ==========	" + Res.RunTime + " ms
    ");
                return Res;
            }

    还有很多想做的啊,

    比如说默认值这个玩意儿我不知道应该如何绑定到文本框里,

    现在还没做链接数据库,

    我还想记录每一次运行的参数,弄个下拉框,选一下就可以直接绑定一起曾经输入过的参数,这样也是很方便的,

    以后再慢慢加吧,欢迎大佬们指出不足之处,

     码云地址:https://gitee.com/StepDest/FunctionAction

  • 相关阅读:
    php设计模式 — 简单工厂模式(静态工厂方法模式)
    Vue-Router
    各种选项卡
    jq动画
    如何使用swiper写轮播
    Gulp代码压缩
    闭包
    jquery.validation校验
    grunt-js文件压缩
    CSS
  • 原文地址:https://www.cnblogs.com/Onlooker/p/9740016.html
Copyright © 2011-2022 走看看