I recently found myself wanting to rename all the fields in several large projects to conform to a new naming scheme.  Sadly Resharper was not helpful here, so I turned to a combination of Regex and Powershell. 

Our team has recently revised our 7 year-old coding guidelines.  One of the guidelines that was changed concerned field naming.  Our previous guideline stated that fields should use an ‘m’ prefix with Pascal Casing, creating fields of the form “mFieldName”.  At the time the guidelines were written, .NET was still young, and the community hadn’t yet settled on a widely accepted standard.  Since then, most of the community seems to have gravitated towards using an underscore prefix for fields with camel casing, creating fields of the form “_fieldName”.  We finally decided to bite the bullet and switch our guideline to conform to the more generally accepted convention.  The problem is that we have a lot of code that uses the old style.  By “a lot”, I mean probably in the neighborhood of 100k lines of code across the various projects. 

One solution we discussed was to just leave existing fields alone, and only use the new convention when creating new code.  This would work, but since we use Resharper to enforce the naming conventions, it would create a lot of false warnings.  It also meant that we would be introducing some inconsistency into our code base, which we wanted to avoid.

One avenue that we explored was using Resharper to update all existing field names.  The Patterns functionality introduced in Resharper 5.0 might be able to accomplish this, but I was unable to figure out how. 

When that failed, James suggested that I try using Regex.  It took a bit of Powershell hacking, but I managed to come up with a script that worked.  I’ve applied it across thousands of classes, and it *appears* to rename things correctly.  Here’s the script:

   1: $regex = New-Object System.Text.RegularExpressions.Regex "(?:\W(?<name>m[A-Z][a-zA-Z0-9_]+))"
   2: Get-ChildItem * -Recurse -Include *.cs | ? {$_ -notmatch 'Resharper' } | ? {$_ -notmatch 'DecompilerCache' } | ForEach-Object { 
   3:     $text = [System.IO.File]::ReadAllText($_.FullName)
   4:     $matches = $regex.Matches($text)
   5:     $matches | ForEach-Object { 
   6:         $toReplace = $_.Groups["name"].Value 
   7:         $newValue = "_" + [System.Char]::ToLower($toReplace[1]) + $toReplace.Substring(2)
   8:         $text = [System.Text.RegularExpressions.Regex]::Replace($text, "(?:(?<prefix>\W)" + $toReplace + ")", "`${prefix}$newValue")
   9:     } 
  10:     [System.IO.File]::WriteAllText($_.FullName, $text)
  11: }

The first line creates a new Regex object that will match things of the form “mFieldName” that are prefixed by a non-word character.  Next, the script recursively iterates through C# files, ignoring the contents of the Resharper directory as well as the DecompiledSources directory.  For each matched file, the script grabs the full text from the file, finds the field names that need to be changed (if any), then uses a different regex to replace the field name in the text.  Note that simple string.Replace won’t work here since the replacement must account for the non-word character that prefixes the filed name, otherwise things such as “NumWhatever” will be transformed as well (as I found out with an earlier version of this script).  Finally, the modified text is written back to the file.

It’s not terribly pretty, and it certainly isn’t innovative, but it did save me quite a bit of time this morning. 🙂