如何用C#写个命令行解析工具 - SimpleParser

使用场景

在工作中经常遇到需要写一个小工具之类的程序,带UI或者不带UI的程序在启动时可能会需要指定启动参数来初始化程序运行的状态,用于命令行解析的有很多开源并且完善的命令行解析工具库,大致搜索了下:.NetCode的System.Commandline、commandlineparser/commandline 都很不错。但是这些都会引入额外的dll文件,这就让本来简单的事情变得稍微有点复杂了。其实我需要传的参数都相当简单,本可以直接使用main函数传入的参数列表,但时间久了是肯定记不住参数的顺序的,因此有了SimpleParser。

满足两个条件的就可以考虑使用:

  1. 工具使用的参数相对简单

  2. 不想要引入额外的dll

具体实现

实现比较简单,但是决定写这个也是有懒得花时间去学习已有的框架的原因,当时写这个也就不到一个小时左右

主要就两点:

  1. 在参数类的属性上面标识元数据

  2. 使用反射机制创建参数类型实例

解析元数据特性类

该自定义特性类用户在类的属性上设置元数据

    public class ParseAttribute : Attribute
    {
        public string name = string.Empty;
        public string help = string.Empty;
        public string defaults = string.Empty;

        public ParseAttribute(string name, string help = null)
        {
            this.name = name; 
            this.help = help;
        }
    }

使用方式:

目前只实现了枚举、跟字符串的解析,其他基础类型、自定义类型也可以同理实现

    public enum ActionOptions 
    {
        LIST,
        ADD,
        DEL,
        NONE
    }

    public class TestArgs
    {
        [Parse("-a")]
        public ActionOptions action = ActionOptions.ADD;

        [Parse("-v","value of action paths")]
        public string value ="None";

    }

反射创建参数实例

先通过传入的目标new一个参数实例,然后将参数列表根据flag标识‘-’转成Dictionary,最后获取实例每个属性上的ParseAttribute然后通过反射机制将参数值设置到实例的属性上面。

public class SimpleParser
    {
        public static T Parse<T>(string[] args) where T : new()
        {
            T arg_obj = new T();
            Dictionary<string, string> arg_values = new Dictionary<string, string>();
            int len = args.Length;
            Type type = null;
            for (int i = 0; i < len; ++i)
            {
                var arg_name = args[i];
                if (arg_name.Equals("--help"))
                {                    
                    ShowHelp(arg_obj);
                    //Environment.Exit(0);
                    return default(T);
                    //return arg_obj;
                }

                if (arg_name.StartsWith("-") && i < len - 1)
                {
                    var value = args[i + 1];
                    if (value.StartsWith("-"))
                    {
                        arg_values[arg_name] = string.Empty;
                        continue;
                    }
                    arg_values.Add(arg_name, value);
                    ++i;
                }
            }

            if (type == null)
            {
                type = arg_obj.GetType();
            }           
          
            var fields = type.GetFields();
            foreach (var field in fields)
            {
                var attr = field.GetCustomAttributes(typeof(ParseAttribute), false).FirstOrDefault() as ParseAttribute;
                if (attr != null)
                {
                    var arg_key = attr.name;
                    string arg_value = string.Empty;
                    if (arg_values.TryGetValue(arg_key, out arg_value))
                    {
                        object val = null;
                        if (field.FieldType.IsEnum)
                        {
                            val = Enum.Parse(field.FieldType, arg_value, true);
                        }
                        else
                        {
                            val = Convert.ChangeType(arg_value, field.FieldType);
                        }
                        //Convert.
                        field.SetValue(arg_obj, val);
                    }
                }
            }
            //properties.att
            return arg_obj;
        }

查看帮助信息

所有参数的帮助信息都定义在了属性的自定特性上面,遍历处理打印到控制台后就行。如果该属性时枚举类型再将所有枚举名称打印出来。

 private static void ShowHelp<T>(T  obj)
        {
            var type = obj.GetType();
            Console.Out.WriteLine("Help : < "+ type.Name +" >");
            var fields = type.GetFields();
            foreach (var field in fields)
            {
                var attr = field.GetCustomAttributes(typeof(ParseAttribute), false).FirstOrDefault() as ParseAttribute;
                if (attr != null)
                {
                    var arg_key = attr.name;
                    Console.Out.WriteLine("    " + arg_key + "  " + field.Name);

                    if (!string.IsNullOrEmpty(attr.help))
                    {
                        Console.Out.WriteLine("      Help : " + attr.help);
                    }

                    var value = field.GetValue(obj);
                    var default_str = string.Empty;
                    if (value == null)
                    {
                        default_str = "None";
                    }
                    else
                    {
                        default_str = value.ToString();
                    }
                    Console.Out.WriteLine("      Default : " + default_str);

                    var fieldType = field.FieldType;
                    if (fieldType.IsEnum)
                    {
                        var enums = fieldType.GetEnumNames();
                        Console.Out.Write("      Options : [");
                        int cnt = enums.Length;
                        Console.Out.Write(enums[0]);

                        for(int i=1;i<cnt;++i)
                        {
                            Console.Out.Write( " | " + enums[i]);
                        }
                        Console.Out.WriteLine("]");
                    } 
                }
            }
        }

结语

在简单场景很实用的命令行解析工具没有任何依赖项,所有代码都贴出来了。喜欢的话就收藏点个赞吧

公告
本博客基于TinyBlog搭建,关注公众号CoderThing