Wednesday, August 6, 2008

Making a MatchEvaluator Delegate "take" more parameters

So I was coding away a few months ago and was planning on using good old Regex.Replace() to process the contents of some files. But I needed it to be a little more robust in how it decided what text would be substituted in for a match. A quick search turned up the MatchEvaluator option. I had never used it before but is sounded like it would fit the bill perfectly. All you do is give it a delegate method to call when your regular expression finds a match. When your delegate method is invoked you can do anything you want to figure out WHAT string you want to pass back as the Regex match.

That led to some test code that looked like this:
   1:  class DelegateTest
   2:      {
   3:          public DelegateTest() { }
   4:          
   5:          public void Go()
   6:          {
   7:              MatchEvaluator oEval = new MatchEvaluator(DoSomeWork);
   8:              string strResult = Regex.Replace("This Is Test Content", "Test", oEval, RegexOptions.IgnoreCase);
   9:          }
  10:   
  11:          private string DoSomeWork(Match m)
  12:          {
  13:              string strTransformedText = string.Empty;
  14:              strTransformedText = "ABC";
  15:              return strTransformedText;
  16:          }
  17:   }

It worked great and I figured that was easy, cool I do that. Then a few days later I came to the point where I was ready to actually write the logic that would go into the delegate method. That's when I hit a snag. The MatchEvaluator delegate has ONE very specific signature it must adhere to. It only takes ONE parameter, a Match object. I needed access to a collection of objects that had been constructed prior to the Regex call. I tried making the object collection global but got an error about it not being in the current context, damn. I feared I was screwed and would have to take a more manual parsing approach.

I decided to do a quick check for delegate definition options and found
this post talking about anonymous methods. "Hot damn" I though, that just may work. I defined the delegate as an anonymous method and CHA-CHING!, I could use variables that where in scope just like normal. I end up factoring out the creation of the MatchEvaluator and its anonymous delegate into its own method because it basicly looked like a giant inline function defined in the middle of all my code. Not easy on the eyes.

After refactoring it ended up looking something like this:

   1:  class DelegateTest
   2:      {
   3:          public DelegateTest() { }
   4:          
   5:          public void Go()
   6:          {
   7:              string strImportantInfo = "XYZ";
   8:              MatchEvaluator oEval = GetEvaluator(strImportantInfo);
   9:              string strResult = Regex.Replace("This Is Test Content", "Test", oEval, RegexOptions.IgnoreCase);
  10:          }
  11:   
  12:          private MatchEvaluator GetEvaluator(string strImportantStuff)
  13:          {
  14:              //Return a MatchEvaluator that is defined using an anonymous delegate method.
  15:              //This allows us to have access to strImportantStuff within the delegate.
  16:              return new MatchEvaluator(delegate(Match m)
  17:              {
  18:                  string strTransformedText = string.Empty;
  19:                  strTransformedText = "ABC";
  20:                  strTransformedText += strImportantStuff;
  21:                  return strTransformedText;
  22:              }
  23:              );
  24:          }
  25:      }

It worked perfectly! I had access to my local objects from within the delegate for the MatchEvaluator as long as I passed them into the creatoin method. No need to refactor my design. Yip-Yip.

No comments: