解析和执行命令
Parsing and Acting on commands
我正在为我的项目制作一个调试控制台,从 TextBox
获取输入,等等
但是我想知道除了一个巨大的 switch 语句是否有任何替代方法:
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;
}
}
}
}
您可以这样做:
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
}
不是真的...我唯一会做的就是将不同类型的命令分成不同的方法以使其更多 streamlined/elegant,并使用通用集合来存储适用于每种类型的命令.
示例:
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();
类似的东西...您所走的路线只会留下优雅和代码整洁。
解析某种语言基本上本身就是一门完整的学科,所以这个问题相当广泛。
语言词法分析器和解析器通常创建命令的树结构,这些命令以保留关键字和参数分隔。保留关键字包含例如命令。 (例如类C语言中的switch
、if
、goto
等)
问题是,这些命令的选择方式非常理想,因此它们相互独立。这意味着关键字本身会引发非常不同的处理。通过参数进行微调。
如果这适用于您的命令,则您没有太多选择来提供处理每个命令的独立方法。例如,JavaCC(JavaCompiler-Compiler)生成一个代码库,其中包含生成指令树的相当大的开关案例。然后由用户评估提供的指令树,这通常是通过处理关键字的单个对象完成的 - 因此可能会有一个 class 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
}
}
至于区分关键字,有多种方法:
一个大的 switch 语句。
持有一个Dictionary<string, Type>
,您可以从中查找处理命令的类型。因此,例如:"Example1 abcde 12345" 将查找 "Example1",在字典中创建该类型的实例并使用参数 "abcde" 和“12345”填充它。
一个相当大胆的方法是通过您的代码反映一个可以处理命令的class。
您将拥有一个类似于 IBaseCommand
的界面,您的所有命令 classes 都从该界面派生。
// 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;
}
}
}
您的目标是使用尽可能少的命令,并通过参数执行尽可能多的操作。
一年前我解决了同样的问题。因此,我将使用我的代码作为示例来解释它是如何工作的,这样您就会知道如何设计命令行解析器以及如何解决您的问题。
作为 OwlSolo 已经 你需要一个基础 class 或一个能够接受参数并执行一些逻辑的接口。
在 Cmd.Net 的情况下是 Command
class:
public abstract class Command
{
protected Command(string name);
public string Name { get; }
public abstract void Execute(ArgumentEnumerator args);
}
class 有两个成员:
- 为命令提供名称的属性
Name
- 接受参数并执行业务逻辑的方法
Execute
。
ArgumentEnumerator
拆分提供的 string
就像上面在 OwlSolo 的代码中提到的 string.Split
方法,但在 a more complex way 中。它生成参数名称及其值的键值对。
例如,您有一个如下所示的字符串:
"/namedArg:value1 value2"
将被解析成两对。第一对是命名参数,名称为 "namedArg"
,值为 "value1"
。第二个是未命名的参数(名称等于 string.Empty
),值为 "value2"
.
命名参数的目的是允许重新排列它们并使其中一些可选。这应该会提高可用性。
现在我们想要一个命令集合,您可以通过名称更快地从中获取其中一个命令。 Dictionary<string, Command>
是最好的选择,但让我们进一步了解并创建一个将控制权转移到子命令的命令。所以我们将能够像 netsh
中那样构建命令 categories/hierarchies。
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.cs and in Argument.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;
}
我正在为我的项目制作一个调试控制台,从 TextBox
获取输入,等等
但是我想知道除了一个巨大的 switch 语句是否有任何替代方法:
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;
}
}
}
}
您可以这样做:
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
}
不是真的...我唯一会做的就是将不同类型的命令分成不同的方法以使其更多 streamlined/elegant,并使用通用集合来存储适用于每种类型的命令.
示例:
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();
类似的东西...您所走的路线只会留下优雅和代码整洁。
解析某种语言基本上本身就是一门完整的学科,所以这个问题相当广泛。
语言词法分析器和解析器通常创建命令的树结构,这些命令以保留关键字和参数分隔。保留关键字包含例如命令。 (例如类C语言中的switch
、if
、goto
等)
问题是,这些命令的选择方式非常理想,因此它们相互独立。这意味着关键字本身会引发非常不同的处理。通过参数进行微调。
如果这适用于您的命令,则您没有太多选择来提供处理每个命令的独立方法。例如,JavaCC(JavaCompiler-Compiler)生成一个代码库,其中包含生成指令树的相当大的开关案例。然后由用户评估提供的指令树,这通常是通过处理关键字的单个对象完成的 - 因此可能会有一个 class 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
}
}
至于区分关键字,有多种方法:
一个大的 switch 语句。
持有一个
Dictionary<string, Type>
,您可以从中查找处理命令的类型。因此,例如:"Example1 abcde 12345" 将查找 "Example1",在字典中创建该类型的实例并使用参数 "abcde" 和“12345”填充它。一个相当大胆的方法是通过您的代码反映一个可以处理命令的class。
您将拥有一个类似于IBaseCommand
的界面,您的所有命令 classes 都从该界面派生。
// 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;
}
}
}
您的目标是使用尽可能少的命令,并通过参数执行尽可能多的操作。
一年前我解决了同样的问题。因此,我将使用我的代码作为示例来解释它是如何工作的,这样您就会知道如何设计命令行解析器以及如何解决您的问题。
作为 OwlSolo 已经
在 Cmd.Net 的情况下是 Command
class:
public abstract class Command
{
protected Command(string name);
public string Name { get; }
public abstract void Execute(ArgumentEnumerator args);
}
class 有两个成员:
- 为命令提供名称的属性
Name
- 接受参数并执行业务逻辑的方法
Execute
。
ArgumentEnumerator
拆分提供的 string
就像上面在 OwlSolo 的代码中提到的 string.Split
方法,但在 a more complex way 中。它生成参数名称及其值的键值对。
例如,您有一个如下所示的字符串:
"/namedArg:value1 value2"
将被解析成两对。第一对是命名参数,名称为 "namedArg"
,值为 "value1"
。第二个是未命名的参数(名称等于 string.Empty
),值为 "value2"
.
命名参数的目的是允许重新排列它们并使其中一些可选。这应该会提高可用性。
现在我们想要一个命令集合,您可以通过名称更快地从中获取其中一个命令。 Dictionary<string, Command>
是最好的选择,但让我们进一步了解并创建一个将控制权转移到子命令的命令。所以我们将能够像 netsh
中那样构建命令 categories/hierarchies。
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.cs and in Argument.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;
}