【问题标题】:In C#, what is the best way to control flow based on parsing an input string在 C# 中,基于解析输入字符串控制流的最佳方法是什么
【发布时间】:2014-09-11 14:49:03
【问题描述】:

我正在构建一个控制台应用程序来帮助加快我经常做的一些事情。我有一个包含 4 个过程选项的菜单,它们可以转换为我班级中的不同方法。

基本上是这样的:

你想做什么?

1 这个东西

2 那个东西

3 一些东西

4 很酷的东西

0 所有的东西。

输入命令字符串:_

目前我正在检查有效输入:

while (command.IndexOfAny("12340".ToCharArray()) == -1)
{
  //Display the menu and accept input
}

然后控制流:

if (command.IndexOf("1") > 0 )
{
  thisThing();
}

if (command.IndexOf("2") > 0 )
{
  thatThing();
}

if (command.IndexOf("3") > 0 )
{
  someStuff();
}

if (command.IndexOf("4") > 0 )
{
  coolStuff();
}

if (command.IndexOf("0") > 0 )
{
  thisThing();
  thatThing();
  someStuff();
  coolStuff();
}

目标是提供输入并按照指示运行一个或多个进程:

1 : thisThing()

13 : thisThing() 和 someStuff();

42 : thatThing() 和coolStuff();

0 : 运行我定义的所有进程。

有没有办法通过更好的做法来做这样的事情?

【问题讨论】:

  • 老实说,我会在 win 表单或 WPF 中创建一个 gui,然后将按钮单击映射到进程。此外,如果您需要执行 thisThing() => someStuff() => thisThing(),则无法使用此方法运行命令两次。
  • 是的,我也想过使用带有按钮的 GUI,但我想我为了简单起见决定使用控制台。我对这些东西真的很陌生,所以 GUI 的东西让我有点害怕。目前,这只是可以分解成更大事物模块的一小部分——在这一点上,可能应该做这样的事情。现在,它只是一个 .exe 文件,可以帮助我在数据库中设置一个新客户端,其中有几项任务可能需要完成,也可能不需要完成。感谢您的宝贵时间!

标签: c# console string-parsing flow-control


【解决方案1】:

我会创建一个Dictionary<char, DoSomething>

public delegate void DoSomething();

Dictionary<char, DoSomething> commands = new Dictionary<char, DoThing>();
commands.Add('0', new DoSomething(DoAll));
commands.Add('1', new DoSomething(ThisThing));
commands.Add('2', new DoSomething(ThatThing));
commands.Add('3', new DoSomething(SomeStuff));
commands.Add('4', new DoSomething(CoolStuff));

然后,输入验证后

foreach(char c in command.ToCharArray()) 
{  
   // Better check if the input is valid
   if(commands.ContainsKey(c))
       commands[c].Invoke();
}

字典包含,作为键,允许的字符,作为值,一个函数的委托,返回 void,没有参数。现在只需按字符循环输入字符并调用相关方法即可。

请注意,这种方法的选择很少,并不比简单的 if/else if/elseswitch/case 更好。同样通过这种方式,您的用户可以键入 2442 来颠倒方法的执行顺序,这在您的实际上下文中是不允许的。

【讨论】:

  • 这似乎是一个很酷的方法。我想它最终有点类似于我最初解析字符串的方式(每个函数的 if (string.contains("")) 语句),但我喜欢有一个坚实、明显的地方,其中“命令”菜单进行了描述。我需要更多地了解代表。感谢您为我指明方向!
【解决方案2】:

我会创建一个代表字典。字典键将是输入值,委托将执行代码。

C# Store functions in a Dictionary 此处介绍了语法。

通过这种方式,您可以在循环中步进输入字符串并进行字典查找;如果值存在则执行委托,否则跳过。

它并不比 if else if endif 设置好,尽管我发现它更有吸引力和可扩展性。

【讨论】:

  • 感谢您的回答 PhillipH!借助史蒂夫的示例和您的链接,我可以更好地处理一些替代方法来处理此问题。谢谢!
【解决方案3】:

我发现了一种非常好的方法,它基于 Ayende Rahien project 中的正则表达式,他在其中解析 Freedb.org 数据文件的内容

基本上parser 设置Tuple&lt;Regex, Action&lt;contextual class&gt;&gt; 的列表,其中上下文类根据解析器当时所在的位置而变化。然后解析变成尝试将每个元素(在本例中为行)与正则表达式匹配,并在匹配时执行相应的操作。

在您的情况下,您可以将命令链接到多个输入,例如

public class Parser
{
    readonly List<Tuple<Regex, Action>> actions = new List<Tuple<Regex, Action>>();
    public Parser()
    {
        Add(@"^0$", () => { DoSomething(); });
        Add(@"^DoSomething$", () => { DoSomething(); });

        Add(@"^1$", () => { DoSomethingElse() });
        Add(@"^DoSomethingElse$", () => { DoSomethingElse() });

        // etc
    }
    private void Add(string regex, Action action)
    {
        var key = new Regex(regex, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
        actions.Add(Tuple.Create(key, action));
    }

    public void Parse(string text)
    {
        foreach (var action in actions)
        {
            var collection = action.Item1.Matches(text);
            try
            {
                action.Item2();
            }
            catch (Exception e)
            {
                // log?
                throw;
            }
        }
    }
}

它允许您使用不同的输入运行所需的方法。

【讨论】:

  • 我不知道为什么这个评价不高。是因为它针对特定问题进行了过度设计吗?我非常幸运能够遇到它,因为它正是我所需要的!
  • 谢谢@JesseSmith。在这种情况下,它可能会过度设计,但它是一种非常简单且可重用的解析数据文件的方法。很高兴它可以提供帮助,并毫不犹豫地推广它的用途
【解决方案4】:

我会创建一个接口,所有命令都从该接口派生,并有一个 switch 语句确定要执行的操作。 类似的东西......

public abstract class ISystem
{
    public abstract void DoSomething();
}

public class ThisThing : ISystem
{
    public override void DoSomething()
    {
        Console.WriteLine("Do This Thing");
    }
}

public class ThatThing : ISystem
{
    public override void DoSomething()
    {
        Console.WriteLine("Do That Thing");
    }
}

static void Main(string[] args)
{
    var input = "-1";
    while(/*input is invalid*/)
    {
        // get input from user
    }

    var thisThing = new ThisThing();
    var thatThing = new ThatThing();

    switch(input)
    {
        case "1":
           {
               thisThing.DoSomething();
               break;
           }
        case "2":
           {
               thatThing.DoSomething();
               break;
           }
        case "0":
           {
               var commands = new List<ISystem>();
               commands.Add(thisThing);
               commands.Add(thatThing);

               // Execute all commands
               commands.ForEach(system => system.DoSomething());
               break;
           }
    }
}

这里的好处是每次添加新命令时只需要实现DoSomething()方法,然后创建一个类var someStuff = new SomeStuff();的实例然后添加到列表中就很简单了case "0":中的命令数

加上 IMO,其意图比管理字典等更清晰。

【讨论】:

  • 嗨,卡尔,感谢您抽出宝贵时间。我应该指定所有方法本质上都是非常程序化的,并且当前设置为静态。因此,在您的示例中,您将使用 .DoSomething() 方法将它们转换为对象并将各种过程转换为对象?抱歉,我对编程架构和 c# 真的很陌生,所以我并不完全遵循。
  • 是的,就是这样。在我的示例中,您为您拥有/需要/想要的每个命令创建一个实例,并根据用户输入的内容调用 that commands .DoSomething() 方法。如果您希望调用 all 的命令,因为它们都实现相同的接口,那么您不需要知道或关心对象类型是什么(ThisThing、ThatThing、SomeStuff)和只是有一个接口类型的列表(在我的例子中是ISystem)这就是为什么你可以只做单行commands.ForEach(system =&gt; system.DoSomething());我希望能回答你的问题?
猜你喜欢
  • 2011-09-05
  • 2011-03-13
  • 2012-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-04
相关资源
最近更新 更多