Steinar Herland
Steinar Herland

Reputation: 1214

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??)

Upvotes: 10

Views: 3565

Answers (1)

Lucas Trzesniewski
Lucas Trzesniewski

Reputation: 51330

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.

Upvotes: 21

Related Questions