Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to rewrite Regex.Replace (due to async api)

I have a function ReplaceParameters that replaces values in a string by using Regex.Replace. This has been working fine, but now the api that gets replacement-string has become async-only. This is a repro of the current code:

public static string ReplaceParameters(string query)
{
    var result = Regex.Replace(query, @"(?<parameter>\|\w+\|)", ReplaceParameterEvaluator,
                                         RegexOptions.ExplicitCapture);

    return result;
}

private static string ReplaceParameterEvaluator(Match parameterMatch)
{
    var parameter = parameterMatch.Groups["parameter"].Value;
    return GetReplacement(parameter);
}

private static string GetReplacement(string parameter)
{
    //...
}

Since the (new) GetReplacement function now is returning a Task instead of string: private static async Task<string> GetReplacementAsync(string parameter) the ReplaceParameterEvaluator function can't be made compatible with the MatchEvaluator delegate.

Sinc this has to run on a web-server and not cause dead-locks, i cant use any of the dirty async-to-sync hacks like for example this: (use .Result) var replacedQuery = Regex.Replace(query, @"(?<parameter>\|\w+\|)", match => ReplaceParameterEvaluatorAsync(match).Result, RegexOptions.ExplicitCapture);

Is it possible to rewrite the function to find all the texts, and then replace them? Could Regex.Matches be used in some way?

(Seriously though, why is there not a Regex.ReplaceAsync function??)

like image 926
Steinar Herland Avatar asked Oct 08 '15 11:10

Steinar Herland


Video Answer


1 Answers

It's simple enough to build your own extension method for this:

public static async Task<string> ReplaceAsync(this Regex regex, string input, Func<Match, Task<string>> replacementFn)
{
    var sb = new StringBuilder();
    var lastIndex = 0;

    foreach (Match match in regex.Matches(input))
    {
        sb.Append(input, lastIndex, match.Index - lastIndex)
          .Append(await replacementFn(match).ConfigureAwait(false));

        lastIndex = match.Index + match.Length;
    }

    sb.Append(input, lastIndex, input.Length - lastIndex);
    return sb.ToString();
}

It's straightforward:

  • Copy unmatched chunks of text as-is to a StringBuilder
  • Append the result of the callback function where text is matched

This is just a convenience method for calling async callbacks. It's not in the API because regex matching and replacement is a CPU-bound operation, which is naturally not asynchronous.

like image 113
Lucas Trzesniewski Avatar answered Oct 03 '22 21:10

Lucas Trzesniewski