【问题标题】:Choosing appropriate method using pattern matching in C#在 C# 中使用模式匹配选择适当的方法
【发布时间】:2016-03-24 14:24:58
【问题描述】:

目前正在用 C# 做一个大学项目,其中包括将一种形式的代码转换为另一种形式的代码,这涉及从许多可用的方法中选择合适的方法/函数。这里的问题是,要使用任何模式匹配技术而不是使用许多 IF ELSE 语句来实现这一点。

现在我已经使用嵌套的 IF ELSE 语句实现了这一点,它填充了整个程序并且在完成时看起来像幼稚的代码。

当前实现:--

输入:

//stored in list<string>
get(3 int)      //type1
get(int:a,b,c)  //type2
get(name)       //type3
//list passed to ProcessGET method

使用 if else :

public string ProcessGET(List<string> inputData)
{
      foreach(var item in inputData)
      { 
         if (inputData.item.Split('(')[1].Split(')')[0].Contains(':'))
         {
            return Type2 result;
         }
         else if (!inputData.item.Split('(')[1].Split(')')[0].Contains(':') && Convert.ToInt32(inputData.item.Split('(')[1].Split(')')[0].Split(' ')[0])>0)
         {
            return Type1 result;
         }
         else
         {
            return Type3 result;
         }
      }   
}

我希望它是这样的,

/stored in list<string>
get(3 int)      //type1
get(int:a,b,c)  //type2
get(name)       //type3
//list passed to ProcessGET method


public string ProcessGET(List<string> inputData)
{
      foreach(var itm in inputData)
      { 
        // call appropriate method(itm) based on type using some pattern matching techniques
      }   
}

string Method1(var data)
{
    return result for type1;  
}   
string Method2(var data)
{
    return result for type2;  
}
string Method3(var data)
{
    return result for type3;  
}

通常情况下,我的程序主要针对各种类型的输入关键字(如“get”、“output”、“declare”等)执行此类工作……其中 Get 被转换为 Scanf 语句,输出为 printf 语句等等在。 在这种情况下,如果我使用 IF ELSE,我的项目充满了 If else 语句。

因为我刚开始学习 C#,我不知道这样的东西是否存在(谷歌搜索但没有找到我想要的东西),所以任何关于这个问题的帮助将非常有帮助(有用)进一步发展。

非常感谢。

【问题讨论】:

  • 你能告诉我们你得到的实际代码吗? function for Type3(var data) 不是有效的 C#。我也很难理解get(3 int) 的意思。你是说重载吗?
  • dynamic 关键字作为返回类型允许您返回任意值。然后,您可以使用 typeof 运算符检查返回的内容的类型,也许可以尝试深入研究该材料,看看是否可以在您的情况下使用它。 (public dynamic ProcessGET() 可以毫无问题地返回 Type1Type2Type3 对象。)
  • @Rob - 那些 get() 输入法是我自己的自定义语法。
  • @MaximilianGerhardt - 对不起,如果我的意思不正确。实际上我总是从所有方法返回字符串值。
  • 转换代码的更好方法是构建(或使用现有的)解析器,它会在内存中构建一个抽象语法树,然后对其进行转换使用语法树上的 '访问者模式' 到选择的输出。这可能超出了这个项目,但值得阅读这些概念以了解解决问题的更好方法。

标签: c# asp.net .net pattern-matching


【解决方案1】:

解决这个问题的另一种通用方法是引入一个接口,比如IMatcher。该接口有一个方法Match,它返回您的类型或完全转换的行。

您创建多个实现IMatcher 的类。

然后你的主循环变成:

var matchers = new [] { new MatcherA(), new MatcherB(), ... };

foreach (string line in input)
  foreach (matcher in matchers)
  {
     var match = matcher.Match(line);
     if (match != null) return match;
  }

不再有大的 if 语句。每个匹配器都有自己的小类,您可以为每个匹配器编写单元测试。此外,使用 RegEx 使您的匹配器更简单。

【讨论】:

  • 是的,这减少了。将尝试使用这个概念。感谢您的回答。
【解决方案2】:

我会在这里留下一些建议,您可以看看。这是一些基本代码。

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace TestStuff
{
    class Program
    {
        //Input string should be of the form "<type>:<index>"
        static dynamic GiveMeSomethingDynamic(string someInput)
        {
            /* predefined arrays sothat we can return something */
            string[] _storedStrings = { "Word 1", "word 2", "word 3" };
            int[] _storedInts = { 1, 2, 3 };
            float[] _storedFloats = { 3.14f, 2.71f, 42.123f };

            /* Parse the input command (stringly typed functions are bad, I know.) */
            string[] splitted = someInput.Split(':');
            string wantedType = splitted[0];
            int index = int.Parse(splitted[1]);

            /* Decide what to return base on that argument */
            switch (wantedType)
            {
                case "int":
                    return _storedInts[index];
                case "string":
                    return _storedStrings[index];
                case "float":
                    return _storedFloats[index];

                //Nothing matched? return null
                default:
                    return null;
            }
        }

        static void Main(string[] args)
        {
            /* get some return values */
            dynamic firstOutput = GiveMeSomethingDynamic("string:0");
            dynamic secondOutput = GiveMeSomethingDynamic("int:1");
            dynamic thirdOutput = GiveMeSomethingDynamic("float:2");

            /* Display the returned objects and their type using reflection */
            Console.WriteLine("Displaying returned objects.\n" +
                              "Object 1: {0}\t(Type: {1})\n" +
                              "Object 2: {2}\t\t(Type: {3})\n" +
                              "Object 3: {4}\t\t(Type: {5})\n",
                              firstOutput, firstOutput.GetType(),
                              secondOutput, secondOutput.GetType(),
                              thirdOutput, thirdOutput.GetType());

            /* Act on the type of a object. This works for *all* C# objects, not just dynamic ones. */
            if (firstOutput is string)
            {
                //This was a string! Give it to a method which needs a string
                var firstOutputString = firstOutput as string; //Cast it. the "as" casting returns null if it couldn't be casted.
                Console.WriteLine("Detected string output.");
                Console.WriteLine(firstOutputString.Substring(0, 4));
            }

            //Another test with reflection. 
            Console.WriteLine();

            //The list of objects we want to do something with
            string[] values = { "string:abcdef", "int:12", "float:3.14" };
            foreach(var value in values)
            {
                /* Parse the type */
                string[] parsed = value.Split(':');
                string _type = parsed[0];
                string _argument = parsed[1];

                switch (_type)
                {
                    case "string":
                        //This is a string.
                        string _stringArgument = _argument as string;
                        Method1(_stringArgument);
                        break;
                    case "int":
                        //Do something with this int
                        int _intArgument = int.Parse(_argument);
                        Method2(_intArgument);
                        break;
                    case "float":
                        float _floatArgument = float.Parse(_argument);
                        Method3(_floatArgument);
                        break;

                    default:
                        Console.WriteLine("Unrecognized value type \"{0}\"!", _type);
                        break;
                }

            }


            Console.ReadLine();
        }

        public static void Method1(string s) => Console.WriteLine("String Function called with argument \"{0}\"", s);
        public static void Method2(int i) => Console.WriteLine("int Function called with argument {0}", i);
        public static void Method3(float f) => Console.WriteLine("float Function called with argument {0}", f);
    }
}

由函数GiveMeSomethingDynamic() 给出的第一种方法依赖于dynamic 关键字,它可以返回任意类型。根据输入字符串,它可以返回stringintfloat。该方法在Main() 函数中调用,并使用例如检查返回对象的类型。 firstOutput is string (is operator). It could have also been done withif(firstOutput.GetType() == typeof(string))`。

第二种方法是经典的“解析和转换”技术。我们解析&lt;type&gt;:&lt;value&gt; 格式的输入字符串,然后使用转换或解析的参数调用不同的函数。这也许就是你想要的。

还有一种“hacky”方式赋予函数任意类型。在那里,您可以只在输入参数上使用 dynamic 关键字,如

 public dynamic superDynamic(dynamic inputVar) 
 {
    //Figure out the type of that object
    //return something dynamic
 } 

“老派”方法(不使用dynamic)将只将object 类型传递给每个函数,但解析是等效的(if(someArgument.GetType() == typeof(string))...)。

希望这能给您一些关于如何解析这些字符串、将它们转换为不同类型并使用它调用不同函数的想法。

【讨论】:

  • 感谢您提供大量有用的附加信息。会弄清楚我是否可以使用那些动态类型
【解决方案3】:

所以类型作为字符串存储在列表中,对吗?并且你想根据字符串的值调用不同的函数?

以下是我将如何完成您的代码:

  1. 创建接口:

    public interface IMyType 
            {
                string Result(); 
                string Input {get; set;}
    
            }
    
    1. 以及实现它的三个类:

       public class Type1 : IMyType
          {
              public string Result()
              {
                  // do something
              }
              public string Input {get; set;}
      
      
          }
      

      (对 Type2 和 Type3 重复)

3.然后创建一个返回这三种类型之一的方法 基于匹配您的字符串输入的模式

    public IMyType GetAppropriateType(string input)
    { 
    if (inputData.item.Split('(')[1].Split(')')[0].Contains(':'))
             {
                return new Type2 {Input = input};
             }
    //etc
    }

    public string ProcessGET(List<string> inputData)
    {
          foreach(var itm in inputData)
          { 
             IMyType type = GetAppropriateType(itm);
             type.Result();

          }   
    }

可能也值得为您的字符串匹配查看正则表达式

【讨论】:

  • 感谢您的解决方案。我认为,与直接使用“IF ELSE”语句相比,这种方法绝不会减少“IF”语句的使用数量,但它有助于在深层隐藏“IF”语句的大量使用。 ProcessGET() 方法的 foreach(...) 循环中的 3 个条件检查语句(IF、IF ELSE、ELSE)被移动到执行相同操作的单独方法中。但是,正如我所说,它有助于隐藏/分离这些条件语句。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多