解析和执行命令

时间:2015-12-23 15:12:29

标签: c# parsing command-line command

我正在为我的项目制作一个调试控制台,从TextBox等获取输入

但是我想知道是否有任何替代方案只需要一个巨大的开关声明:

switch(command.ToLower) {
case "example":
    // do things...
    break;
case "example2":
    // do things...
    break;
}

我觉得有一个更优雅的解决方案可用,但我的技能无法访问它。

修改 由于@OwlSolo的惊人贡献我现在有我的代码工作,我已经发布了我提交的代码的修改版本,这对我来说是有用的。谢谢@OwlSolo,你是一个传奇打字!

class parseCommand
{
    public static commandBase commandInstance { get; set; }

    public static void parse(string command)
    {
        string[] tokens = command.Split(' '); // tokens[0] is the original command
        object[] parameters = tokens.Skip(1).ToArray();

        List<Type> cmdTypes = System.Reflection.Assembly
            .GetExecutingAssembly()
            .GetTypes()
            .Where(t => typeof(commandBase).IsAssignableFrom(t))
            .ToList();

        foreach(Type derivedType in cmdTypes)
        {
            if (derivedType.Name.ToLower() == tokens[0].ToLower())
            {
                commandInstance = (commandBase)Activator.CreateInstance(derivedType);
                commandInstance.parameters = parameters;
                commandInstance.Execute();
                break;
            }
        }
    }
}

4 个答案:

答案 0 :(得分:3)

解析某种语言本身基本上是一门完整的学科,所以这个问题相当广泛。 语言词法分析器和解析器通常创建命令的树结构,这些命令在保留的关键字和参数中分开。保留关键字包含例如命令。 (例如{C}类语言中的switchifgoto等) 问题是,理想地选择这些命令是为了使它们相互独立。这意味着关键字本身会引发一种截然不同的处理方式。微调是通过参数完成的。

如果这适用于您的命令,则您没有太多选择来提供处理每个命令的独立方法。例如,JavaCC(JavaCompiler-Compiler)生成一个代码库,其中包含生成指令树的相当大的switch case。然后由用户来评估提供的指令树,这通常是通过处理关键字的单个对象来完成的 - 因此可能有一个类IfStatement,它包含许多子指令并处理它的执行。

无论你具体需要什么,真正的工作将是,你如何处理执行而不是你如何区分哪个命令调用哪种行为。

您可能想要的结构可能如下所示:

abstract class BinaryCommand
{
    MyBaseCommand child1;
    MyBaseCommand child2;

    abstract object Execute();
}

class ExampleCommand1 : BinaryCommand
{
    override object Execute()
    {
         //DoStuff1...
    }
}

class ExampleCommand2 : BinaryCommand
{
    override object Execute()
    {
         //Do Somethign else
    }
}

至于区分关键字,有多种方法:

  • 一个大型的开关声明。

  • 持有Dictionary<string, Type>,您可以从中查找处理命令的Type。例如:“Example1 abcde 12345”将查找“Example1”,在字典中创建该类型的实例,并使用参数“abcde”和“12345”进行填充。

  • 一种相当大胆的方式是通过代码反映一个可以处理命令的类 您将拥有一个类似IBaseCommand的接口,您可以从中获取所有命令类。

// Get all the types that derive from your base type
List<Type> commandTypes = Assembly
    .GetExecutingAssembly()
    .GetTypes()
    .Where(t => typeof(IBaseCommand).IsAssignableFrom(t));

foreach (Type derivedType in commandTypes)
{
    // Distinguishing by class name is probably not the best solution here, but just to give you the general idea
    if (derivedType.Name == command.ToLower) 
    {
        // Create an instance of the command type
        IBaseCommand myCommandInstance = Activator.CreateInstance(derivedType);
        //Call the execute method, that knows what to do
        myCommandInstance.Execute();
    }
}

编辑:根据评论中提供的信息,您可以执行以下操作

Interface ICommandBase
{
    object[] parameters {get; set;}
    void Execute();    
}

abstract class InternalCommand : ICommandBase
{
    //Some methods that are common to all your intzernal commands
}

class SetColorCommand : InternalCommand //Or it might derive from ICommandBase directly if you dont need to categorize it
{
     object[] parameters {get; set;}
     void Execute()
     {
         switch (parameters[0])
         {
              case "background":
                  //Set the background color to parameters[1]
              break;
              case "foreground":
                    //...
              break;
         }
     }
}

class SqlCommand : ICommandBase
// Or it might derive from ICommandBase directly if you don't need to categorize it
{
     object[] parameters {get; set;}
     void Execute()
     { 
          //Parameters[0] would be the sql string...
     } 
}

然后通过以下方式解析整个事情:

// Assuming you're getting one command per line and one line is fed to this function
public void ParseCommands(string command)
{
    string[] tokens = command.Split(" ");
    // tokens[0] is the command name
    object[] parameters = (object[])tokens.Skip(1);//Take everything but the first element (you need to include LINQ for this)

    // Get all the types that derive from your base type
    List<Type> commandTypes = Assembly
        .GetExecutingAssembly()
        .GetTypes()
        .Where(t => typeof(IBaseCommand).IsAssignableFrom(t));

    foreach (Type derivedType in commandTypes)
    {
        if (derivedType.Name.ToLower == tokens[0].ToLower) 
        /* Here the class name needs to match the commandname; this yould also be a
           static property "Name" that is extracted via reflection from the classes for 
           instance, or you put all your commands in a Dictionary<String, Type> and lookup 
           tokens[0] in the Dictionary */
        {
            // Create an instance of the command type
            IBaseCommand myCommandInstance = Activator.CreateInstance(derivedType);
            myCommandInstance.parameters = parameters;
            myCommandInstance.Execute(); // Call the execute method, that knows what to do
                 break;
        }
    }   
}

您的目标是尽可能少地执行命令,并通过参数尽可能多地执行。

答案 1 :(得分:1)

不是真的......我唯一要做的就是将不同类型的命令分成不同的方法,使其更加流线型/优雅,并使用泛型集合来存储适用于每种类型的命令。

示例:

List<string> MoveCommands = new List<string>(){"Move", "Copy", "Merge"};
List<string> Actions = new List<string>() {"Add", "Edit", "Delete"};

//.......

if(MoveCommands.contains(inputtext))
    ExecuteMoveCommand();
else if (Actions.contains(inputtext))
    ExecuteActionCommand();
像这样的东西......你所走的路线只会留下优雅和整洁的代码。

答案 2 :(得分:0)

您可以执行以下操作:

var lookup = new Dictionary<string, YourParamType> ()  
{
  { "example", a  },
  { "example2", b },
   ....
};

YourParamType paraType;
if (lookup.TryGetValue(command.ToLower(), out para))   // TryGetValue, on popular      request
{       
   //your code
}

答案 3 :(得分:0)

一年前我解决了同样的问题。因此,我将使用我的代码作为示例解释它是如何工作的,因此您将知道如何设计命令行解析器以及如何解决您的问题。

由于OwlSolo已经said,你需要一个基类或一个能够接受参数并执行某些逻辑的接口。

如果Cmd.NetCommand类:

public abstract class Command
{
    protected Command(string name);
    public string Name { get; }
    public abstract void Execute(ArgumentEnumerator args);
}

该班有两名成员:

  1. 提供命令名称的属性Name
  2. 接受参数并执行业务逻辑的方法Execute
  3. ArgumentEnumerator将提供的string与上述OwlSolo代码中提到的string.Split方法分开,但在a more complex way中。它生成参数名称及其值的键值对。

    例如,您有一个如下所示的字符串:

    "/namedArg:value1 value2"
    

    将被解析为两对。第一对是命名参数,名称为"namedArg",值为"value1"。第二个是未命名的参数(名称等于string.Empty),其值为"value2"

    命名参数的目的是允许重新排列它们并使其中一些可选。这应该可以提高可用性。

    现在我们想要一个命令集合,您可以通过该名称更快地获取其中一个命令。而Dictionary<string, Command>是最好的选择,但让我们进一步看看,并制作一个命令,将控制转移到子命令。因此,我们将能够在netsh中构建命令类别/层次结构。

    public sealed class CommandContext : Command
    {
        public CommandContext(string name);
        public CommandCollection Commands { get; }
        public override void Execute(ArgumentEnumerator args);
    }
    
    public sealed class CommandCollection : KeyedCollection<string, Command>
    {
        public CommandCollection() 
            : base(StringComparer.OrdinalIgnoreCase) 
        { 
        } 
    
        protected override string GetKeyForItem(Command item)
        { 
            return item.Name;
        }
    
        public bool TryGetCommand(string name, out Command command)
        { 
            return Dictionary.TryGetValue(name, out command);
        }
    }
    

    重写的Execute方法将获取第一个参数(如果它未命名)并使用TryGetCommand方法搜索命令。当它找到命令时,它会使用除第一个参数之外的所有参数执行它。如果没有找到命令或第一个参数有名称,那么我们应该显示错误。

    注意由于我们使用StringComparer.OrdinalIgnoreCase,因此我们不应担心传递的name中的字符大小写。

    现在是时候考虑自动化参数解析和转换了。为实现这一目标,我们可以使用反射和TypeConverter s。

    public sealed class DelegateCommand : Command
    {
        public DelegateCommand(Delegate method);
        public Delegate Method { get; }
        public override void Execute(ArgumentEnumerator args);
    }
    

    DelegateCommand的构造函数中,您应该收集有关method参数的信息(名称,默认值,类型转换器等),然后在Execute方法中使用它为method投射和提供参数。

    我省略了实施细节,因为它很复杂,但您可以在DelegateCommand.csArgument.cs中阅读相关内容。

    最后,您将能够在不进行任何解析的情况下执行方法。

    CommandContext root = new CommandContext(
        "root",
        new Command("multiply", new Action<int, int>(Multiplication)),
        new CommandContext(
            "somecontext",
            // ...
            )
        );
    ArgumentEnumerator args = new ("add /x:6 /y:7");
    
    root.Execute(args);
    
    public static void Multiplication([Argument("x")] int x, [Argument("y")] int y)
    {
        // No parsing, only logic
        int result = x * y;
    }