C# 如何解析和执行命令行样式的字符串?
最后我有一个具体的问题,但我想提供大量的背景和背景,以便读者能够理解我的目标 背景 我正在用ASP.NET MVC 3构建一个控制台风格的应用程序。概念本身很简单:从客户端接收命令字符串,检查提供的命令是否存在,以及随命令提供的参数是否有效,执行命令,返回一组结果 内部工作 有了这个应用程序,我决定变得有点创造性。终端风格应用程序最明显的解决方案是构建世界上最大的IF语句。通过IF语句运行每个命令,并从内部调用适当的函数。我不喜欢这个主意。在旧版本的应用程序中,这就是它的运行方式,这是一个巨大的混乱。向应用程序添加功能是非常困难的 经过深思熟虑,我决定构建一个称为命令模块的自定义对象。我们的想法是用每个请求构建这个命令模块。模块对象将包含所有可用的命令作为方法,然后站点将使用反射来检查用户提供的命令是否与方法名称匹配。命令模块对象位于一个名为C# 如何解析和执行命令行样式的字符串?,c#,.net,asp.net-mvc,object,methods,C#,.net,Asp.net Mvc,Object,Methods,最后我有一个具体的问题,但我想提供大量的背景和背景,以便读者能够理解我的目标 背景 我正在用ASP.NET MVC 3构建一个控制台风格的应用程序。概念本身很简单:从客户端接收命令字符串,检查提供的命令是否存在,以及随命令提供的参数是否有效,执行命令,返回一组结果 内部工作 有了这个应用程序,我决定变得有点创造性。终端风格应用程序最明显的解决方案是构建世界上最大的IF语句。通过IF语句运行每个命令,并从内部调用适当的函数。我不喜欢这个主意。在旧版本的应用程序中,这就是它的运行方式,这是一个巨大的
ICommandModule
的接口后面,如下所示
namespace U413.Business.Interfaces
{
/// <summary>
/// All command modules must ultimately inherit from ICommandModule.
/// </summary>
public interface ICommandModule
{
/// <summary>
/// The method that will locate and execute a given command and pass in all relevant arguments.
/// </summary>
/// <param name="command">The command to locate and execute.</param>
/// <param name="args">A list of relevant arguments.</param>
/// <param name="commandContext">The current command context.</param>
/// <param name="controller">The current controller.</param>
/// <returns>A result object to be passed back tot he client.</returns>
object InvokeCommand(string command, List<string> args, CommandContext commandContext, Controller controller);
}
}
此行定义接受所需ID号、可选页码和带有回复值的可选回复命令的TOPIC命令
我目前实现的命令方法如下(方法上方的属性用于帮助菜单信息。帮助命令使用反射来读取所有这些内容并显示有组织的帮助菜单):
那么我很乐意这样做:
ICommandModule
上的InvokeCommand()
InvokeCommand()
执行一些神奇的解析和反射,用正确的参数选择正确的命令方法,并调用该方法,只传递必要的参数Reply“Reply”
如何将Reply内容与Reply变量配对,同时遇到
编号并将其提供给Id
参数
我肯定我现在把你搞糊涂了。让我用一些用户可能传入的命令字符串示例来说明:
TOPIC 36 reply // Should prompt the user to enter reply text.
TOPIC 36 reply "Hey what's up?" // Should post a reply to the topic.
TOPIC 36 // Should display page 1 of the topic.
TOPIC 36 page 4 // Should display page 4 of the topic.
我如何知道向Id
参数发送36?我如何知道将reply与“Hey what's up?”配对,并将“Hey what's up?”作为方法上reply参数的值传递
为了知道调用哪个方法重载,我需要知道提供了多少个参数,以便我可以将该数字与接受相同数量参数的命令方法重载相匹配。问题是,`主题36回答“嘿,怎么了?”实际上是两个论点,而不是三个作为回答和“嘿…”作为一个论点
我不介意将InvokeCommand()
方法膨胀一点(或很多),只要这意味着所有复杂的解析和反射都在那里处理,并且我的命令方法可以保持良好、干净和易于编写
我想我真的只是在这里寻找一些见解。有人有什么创造性的想法来解决这个问题吗?这确实是一个大问题,因为IF语句的参数使得为应用程序编写新命令变得非常复杂。命令是应用程序的一部分,我希望它非常简单,以便可以轻松地扩展和更新。以下是我的应用程序中实际的TOPIC命令方法:
/// <summary>
/// Shows a topic and all replies to that topic.
/// </summary>
/// <param name="args">A string list of user-supplied arguments.</param>
[CommandInfo("Displays a topic and its replies.")]
[CommandArgInfo("ID", "Specify topic ID to display the topic and all associated replies.", true, 0)]
[CommandArgInfo("Page#/REPLY/EDIT/DELETE [Reply ID]", "Subcommands can be used to navigate pages, reply to the topic, edit topic or a reply, or delete topic or a reply.", false, 1)]
public void TOPIC(List<string> args)
{
if ((args.Count == 1) && (args[0].IsLong()))
TOPIC_Execute(args);
else if ((args.Count == 2) && (args[0].IsLong()))
if (args[1].ToLower() == "reply" || args[1].ToLower() == "modreply")
TOPIC_ReplyPrompt(args);
else if (args[1].ToLower() == "edit")
TOPIC_EditPrompt(args);
else if (args[1].ToLower() == "delete")
TOPIC_DeletePrompt(args);
else
TOPIC_Execute(args);
else if ((args.Count == 3) && (args[0].IsLong()))
if ((args[1].ToLower() == "edit") && (args[2].IsLong()))
TOPIC_EditReplyPrompt(args);
else if ((args[1].ToLower() == "delete") && (args[2].IsLong()))
TOPIC_DeleteReply(args);
else if (args[1].ToLower() == "edit")
TOPIC_EditExecute(args);
else if (args[1].ToLower() == "reply" || args[1].ToLower() == "modreply")
TOPIC_ReplyExecute(args);
else if (args[1].ToLower() == "delete")
TOPIC_DeleteExecute(args);
else
_result.DisplayArray.Add(DisplayObject.InvalidArguments);
else if ((args.Count >= 3) && (args[0].IsLong()))
if (args[1].ToLower() == "reply" || args[1].ToLower() == "modreply")
TOPIC_ReplyExecute(args);
else if ((args[1].ToLower() == "edit") && (args[2].IsLong()))
TOPIC_EditReplyExecute(args);
else if (args[1].ToLower() == "edit")
TOPIC_EditExecute(args);
else
_result.DisplayArray.Add(DisplayObject.InvalidArguments);
else
_result.DisplayArray.Add(DisplayObject.InvalidArguments);
}
如您所见,我并不担心如何找到命令方法,因为它已经在工作了。我正在考虑一种解析命令字符串、组织参数,然后使用这些信息选择正确的命令方法/使用反射重载的好方法
最终设计目标
我正在寻找一种非常好的方法来解析我传递的命令字符串。我希望解析器能够识别以下几点:
- 选项。标识命令字符串中的选项
- 名称/值对。识别名称/值对(例如,[page#]我通常使用的解决方案是这样的。请忽略我的语法错误…我使用C#已经有几个月了。基本上,用
查找和虚拟函数调用替换if/else/switchSystem.Collections.Generic.Dictionary
interface ICommand { string Name { get; } void Invoke(); } //Example commands class Edit : ICommand { string Name { get { return "edit"; } } void Invoke() { //Do whatever you need to do for the edit command } } class Delete : ICommand { string Name { get { return "delete"; } } void Invoke() { //Do whatever you need to do for the delete command } } class CommandParser { private Dictionary<string, ICommand> commands = new ...; public void AddCommand(ICommand cmd) { commands.Insert(cmd.Name, cmd); } public void Parse(string commandLine) { string[] args = SplitIntoArguments(commandLine); //Write that method yourself :) foreach(string arg in args) { ICommand cmd = commands.Find(arg); if (!cmd) { throw new SyntaxError(String.Format("{0} is not a valid command.", arg)); } cmd.Invoke(); } } } class CommandParserXyz : CommandParser { CommandParserXyz() { AddCommand(new Edit); AddCommand(new Delete); } }
接口ICommand { 字符串名称{get;} void Invoke(); } //示例命令 类编辑:ICommand { 字符串名称{get{返回“编辑”;} void Invoke() { //执行编辑命令所需的任何操作 } } 类删除:ICommand { 字符串名称{get{返回“delete”;} void Invoke() { //执行delete命令所需的任何操作 } } 类命令分析器 { 私有字典命令=新建。。。; 公共void AddCommand(ICommand cmd) { commands.Insert(cmd.Name,cmd); } 公共void解析(字符串命令行) { string[]args=splitintarguments(命令行);//自己编写该方法:) foreach(args中的字符串arg) { ICommand cmd=commands.Find(arg); 如果(!cmd) { 抛出新的SyntaxError(String.Format(“{0}”不是有效的命令。”,arg
TOPIC 36 reply // Should prompt the user to enter reply text. TOPIC 36 reply "Hey what's up?" // Should post a reply to the topic. TOPIC 36 // Should display page 1 of the topic. TOPIC 36 page 4 // Should display page 4 of the topic.
/// <summary> /// Shows a topic and all replies to that topic. /// </summary> /// <param name="args">A string list of user-supplied arguments.</param> [CommandInfo("Displays a topic and its replies.")] [CommandArgInfo("ID", "Specify topic ID to display the topic and all associated replies.", true, 0)] [CommandArgInfo("Page#/REPLY/EDIT/DELETE [Reply ID]", "Subcommands can be used to navigate pages, reply to the topic, edit topic or a reply, or delete topic or a reply.", false, 1)] public void TOPIC(List<string> args) { if ((args.Count == 1) && (args[0].IsLong())) TOPIC_Execute(args); else if ((args.Count == 2) && (args[0].IsLong())) if (args[1].ToLower() == "reply" || args[1].ToLower() == "modreply") TOPIC_ReplyPrompt(args); else if (args[1].ToLower() == "edit") TOPIC_EditPrompt(args); else if (args[1].ToLower() == "delete") TOPIC_DeletePrompt(args); else TOPIC_Execute(args); else if ((args.Count == 3) && (args[0].IsLong())) if ((args[1].ToLower() == "edit") && (args[2].IsLong())) TOPIC_EditReplyPrompt(args); else if ((args[1].ToLower() == "delete") && (args[2].IsLong())) TOPIC_DeleteReply(args); else if (args[1].ToLower() == "edit") TOPIC_EditExecute(args); else if (args[1].ToLower() == "reply" || args[1].ToLower() == "modreply") TOPIC_ReplyExecute(args); else if (args[1].ToLower() == "delete") TOPIC_DeleteExecute(args); else _result.DisplayArray.Add(DisplayObject.InvalidArguments); else if ((args.Count >= 3) && (args[0].IsLong())) if (args[1].ToLower() == "reply" || args[1].ToLower() == "modreply") TOPIC_ReplyExecute(args); else if ((args[1].ToLower() == "edit") && (args[2].IsLong())) TOPIC_EditReplyExecute(args); else if (args[1].ToLower() == "edit") TOPIC_EditExecute(args); else _result.DisplayArray.Add(DisplayObject.InvalidArguments); else _result.DisplayArray.Add(DisplayObject.InvalidArguments); }
/// <summary> /// Invokes the specified command method and passes it a list of user-supplied arguments. /// </summary> /// <param name="command">The name of the command to be executed.</param> /// <param name="args">A string list of user-supplied arguments.</param> /// <param name="commandContext">The current command context.</param> /// <param name="controller">The current controller.</param> /// <returns>The modified result object to be sent to the client.</returns> public object InvokeCommand(string command, List<string> args, CommandContext commandContext, Controller controller) { _result.CurrentContext = commandContext; _controller = controller; MethodInfo commandModuleMethods = this.GetType().GetMethod(command.ToUpper()); if (commandModuleMethods != null) { commandModuleMethods.Invoke(this, new object[] { args }); return _result; } else return null; }
// Metadata to be used by the HELP command when displaying HELP menu, and by the // command string parser when deciding what types of arguments to look for in the // string. I want to place these above the first overload of a command method. // I don't want to do an attribute on each argument as some arguments get passed // into multiple overloads, so instead the attribute just has a name property // that is set to the name of the argument. Same name the user should type as well // when supplying a name/value pair argument (e.g. Page 3). [CommandInfo("Test command tests things.")] [ArgInfo( Name="ID", Description="The ID of the topic.", ArgType=ArgType.ValueOnly, Optional=false )] [ArgInfo( Name="PAGE", Description="The page number of the topic.", ArgType=ArgType.NameValuePair, Optional=true )] [ArgInfo( Name="REPLY", Description="Context shortcut to execute a reply.", ArgType=ArgType.NameValuePair, Optional=true )] [ArgInfo( Name="OPTIONS", Description="One or more options.", ArgType=ArgType.MultiOption, Optional=true PossibleValues= { { "-S", "Sort by page" }, { "-R", "Refresh page" }, { "-F", "Follow topic." } } )] [ArgInfo( Name="SUBCOMMAND", Description="One of several possible subcommands.", ArgType=ArgType.SingleOption, Optional=true PossibleValues= { { "NEXT", "Advance current page by one." }, { "PREV", "Go back a page." }, { "FIRST", "Go to first page." }, { "LAST", "Go to last page." } } )] public void TOPIC(int id) { // Example Command String: "TOPIC 13" } public void TOPIC(int id, int page) { // Example Command String: "TOPIC 13 page 2" } public void TOPIC(int id, string reply) { // Example Command String: TOPIC 13 reply "reply" // Just a shortcut argument to another command. // Executes actual reply command. REPLY(id, reply, { "-T" }); } public void TOPIC(int id, List<string> options) { // options collection should contain a list of supplied options Example Command String: "TOPIC 13 -S", "TOPIC 13 -S -R", "TOPIC 13 -R -S -F", etc... }
interface ICommand { string Name { get; } void Invoke(); } //Example commands class Edit : ICommand { string Name { get { return "edit"; } } void Invoke() { //Do whatever you need to do for the edit command } } class Delete : ICommand { string Name { get { return "delete"; } } void Invoke() { //Do whatever you need to do for the delete command } } class CommandParser { private Dictionary<string, ICommand> commands = new ...; public void AddCommand(ICommand cmd) { commands.Insert(cmd.Name, cmd); } public void Parse(string commandLine) { string[] args = SplitIntoArguments(commandLine); //Write that method yourself :) foreach(string arg in args) { ICommand cmd = commands.Find(arg); if (!cmd) { throw new SyntaxError(String.Format("{0} is not a valid command.", arg)); } cmd.Invoke(); } } } class CommandParserXyz : CommandParser { CommandParserXyz() { AddCommand(new Edit); AddCommand(new Delete); } }
static Dictionary<string, Action<List<string>>> commandMapper; static void Main(string[] args) { InitMapper(); Invoke("TOPIC", new string[]{"1","2","3"}.ToList()); Invoke("Topic", new string[] { "1", "2", "3" }.ToList()); Invoke("Browse", new string[] { "1", "2", "3" }.ToList()); Invoke("BadCommand", new string[] { "1", "2", "3" }.ToList()); } private static void Invoke(string command, List<string> args) { command = command.ToLower(); if (commandMapper.ContainsKey(command)) { // Execute the method commandMapper[command](args); } else { // Command not found Console.WriteLine("{0} : Command not found!", command); } } private static void InitMapper() { // Add more command to the mapper here as you have more commandMapper = new Dictionary<string, Action<List<string>>>(); commandMapper.Add("topic", Topic); commandMapper.Add("browse", Browse); } static void Topic(List<string> args) { // .. Console.WriteLine("Executing Topic"); } static void Browse(List<string> args) { // .. Console.WriteLine("Executing Browse"); }
public void TOPIC ( [ArgInfo("Specify topic ID...")] int Id, [ArgInfo("Specify topic page...")] int? page) { ... }
string data = null; bool help = false; int verbose = 0; var p = new OptionSet () { { "file=", v => data = v }, { "v|verbose", v => { ++verbose } }, { "h|?|help", v => help = v != null }, }; List<string> extra = p.Parse (args);