Mapping Strings to Functionality Using Regular Exp

2019-05-11 19:32发布

问题:

I have a string and multiple regex's, for example a regex that checks if the string is a number only, if it starts with character X and so on. I have different codes running depending on what regex gets matched, like this:

if (Regex.IsMatch(myString, regex1))
{
    //number
}
else if (Regex.IsMatch(myString, regex2))
{
    //something else
}
else if (Regex.IsMatch(myString, regex3))
{
    //something else
}

and so on. However, this looks very clunky as I have like 10 regex's to go through, so can I do the same thing using switch/case? If so, can I be provided with an example?

I am using .NET 2.0 and WinForms.

回答1:

This cannot be done as you describe because switch can only be used with: bool, char string, int, enum or a corresponding nullable type.



回答2:

You can keep a dictionary of associations - match function and handling logic, then just loop through matches and once it return true - execute associated handling logic so you'll end up with pretty simple foreach loop:

string input = "some input ^&(*729384293";
string numberPattern = "regex to check numbers";
string datePattern = "regex to check date";

// KEY - match regex function
// VALUE - match handling logic
var matchesMap = new Dictionary<Func<bool>, Action>();

matchesMap.Add(
    () => Regex.IsMatch(input, numberPattern),
    () => { /*this code will be called when numer match found*/ });
matchesMap.Add(
    () => Regex.IsMatch(input, datePattern),
    () => { /*this code will be called when date match found*/ });

foreach (var match in matchesMap.Keys)
{
    if (match())
    {
        matchesMap[match].Invoke();
        break;
    }
}


回答3:

The only way to do this is with a switch-true statement (not recommended).

switch (true)
{
    case Regex.IsMatch(myString, regex1):
        // ...
        break;
    case Regex.IsMatch(myString, regex2):
        // ...
        break;
    case Regex.IsMatch(myString, regex3):
        // ...
        break;
    default:
        // ...
        break;
}

EDIT: Buh Buh mentioned the fact that the case expressions must be constant, so the code won't compile.

There is another very roundabout way to solve this. Remember, I am pointing this out not as a recommendation, but as an example of why an if block is better.

If your regexes are in an array regexes, you can do this:

int type = -1;
for (int i = 0; i < regexes.Length; i++)
{
    if (Regex.IsMatch(myString, regexes[i])
    {
        type = i;
        break;
    }
}
switch (type)
{
    case 0:
        // ...
        break;
    case 1:
        // ...
        break;
    case 2:
        // ...
        break;
    default:
        break;
}


回答4:

You could make a more complex regex like /(a)|(b)|(c)/ and then check match.Groups afterwards, which pattern matched:

Match match = Regex(input, @"/(a)|(b)|(c)/");
Foreach (Group g in match.Groups) {
  if (g.Success) {
    // Your code here
  }
}


回答5:

It boils down to how much code you have for each Regex case.

If it's only a line or two of code

I say stick with the if-else. Not only is it more direct and to the point, but it's also easier to read.


If it's complex, or you use the switch several times

If what you have is rather complicated, you might want to look into implementing the Strategy Pattern or something like that.


Bottom Line

There are times where switch statements are fine, but when you find yourself using the same switch multiple times, or there is a good amount of code for each case, the strategy pattern can really help out, especially when it comes to maintainability.



回答6:

One 'non-clunky' solution is to use a simplified version of the Chain Of Responsibility design pattern. In fact, this pattern was made to handle situations exactly like David has described here.

Code

In your case, you simply need to map strings to activities. You can define a base class that iterates through the chain and performs the defined activity when a match is found, or forwards the input on to the next option.

abstract class Option
{
    public void DoActivity(string input)
    {
        Match parse = m_regex.Match(input);
        if (parse.Success)
            Activity(input);
        else if (m_nextOption != null)
            m_nextOption.DoActivity(input);
        else
            Console.WriteLine("No activity for input \"{0}\"", input);
    }

    // Each option defines its own activity; override as necessary.
    protected void Activity(string input)
    {
        Console.WriteLine(
            "For input \"{0}\" --> do activity defined in {1} ...", input, this);
    }

    // The next option in the chain, or null.
    protected Option m_nextOption;

    // Regular expression that matches the input for this option.
    protected Regex m_regex;
}

Then create derived classes for as many options as you need. Each defines:

  1. the regular expression that matches the inputs to handle,
  2. the activity to do for matching inputs (all use a base class method in this simple example), and
  3. the next option to try in the chain, if there is one.

Something like:

internal class OptionOne : Option
{
    public OptionOne()
    {
        m_nextOption = new OptionTwo(); // next option.
        m_regex = new Regex(@"^Option One$", RegexOptions.Compiled);
    }
}
internal class OptionTwo : Option
{
    public OptionTwo()
    {
        m_nextOption = new OptionThree(); // next option.
        m_regex = new Regex(@"^Option Two$", RegexOptions.Compiled);
    }
 }
internal class OptionThree : Option
{
    public OptionThree()
    {
        m_nextOption = null; // no other options.
        m_regex = new Regex(@"^Option Three$", RegexOptions.Compiled);
    }
}

Usage

 // Possible input strings.
 string[] myStrings = { "Option One", "Option Two",
                        "Option Three", "Matches No Options" };

 // Do each input's activity ...
 Option options = new OptionOne();
 foreach (var myString in myStrings)
 {
     options.DoActivity(myString);
 }

Output

For input "Option One" --> do activity defined in StackOverflow.OptionOne ...
For input "Option Two" --> do activity defined in StackOverflow.OptionTwo ...
For input "Option Three" --> do activity defined in StackOverflow.OptionThree ...
No activity for input "Matches No Options"

Another advantage to using this pattern is that if you need to add additional options later, you just need to define additional derived classes and hook them into the chain. The calling code doesn't need to change at all!