Sunday, March 25, 2012

System.String missing overloads

The .NET string class is pretty light on methods, which is actually a really handy thing.  Most everything you need is there, and if it isn't you can add to it with extension methods.  For anyone who made the transition from VB and its Mid(), Left(), Right(), InStr() methods, this was a welcome change.  Substring, Replace, IndexOf, TrimEnd, Split - these are all very discoverable and easy to use methods that can be built upon to create more advanced functions.  Trouble is, there are some very conspicuous omissions of overloads - in other words, methods that are already on System.String that are missing obvious ways of calling them because Microsoft didn't provide them.  I kept waiting for these to show up in the framework, but after 12 years of .NET development I've given up hope that Microsoft will ever provide these.  So here they are - the missing overloads for methods MS already provided.

Substring - Substring() is the most error prone method on String.  It throws exceptions all the time if you didn't get your math just perfect.  Here's an overload for Substring that offers an extra variable to ensure your Substring() method never fails:

public static string Substring(this string s, int startIndex, int length, bool neverFail) {
   if (neverFail == false) return s.Substring(startIndex, length);
   startIndex = (startIndex < 0 ? 0 : (startIndex > s.Length ? s.Length : startIndex));

   if (length < 0) length = 0;
   if ((startIndex + length) > s.Length) {
      length = s.Length - startIndex;
   }

   return s.Substring(startIndex, length);
}

TrimStart and TrimEnd - There's a nice little zero parameter method called Trim() on string, but TrimStart() and TrimEnd() take an array of characters to trim.  The most common trim is whitespace, and there's no easy way to just trim that at the beginning or end of the string.  It would have been so easy to leave a parameterless TrimStart()/TrimEnd(), but alas.  So here it is:

public static string TrimEnd(this string s) {
   if (s == null) return null;
   var buf = new StringBuilder(s);
   while (buf.Length > 0 && Char.IsWhiteSpace(buf[buf.Length - 1])) {
      buf.Remove(buf.Length - 1, 1);
   }
   return buf.ToString();
}

public static string TrimStart(this string s) {
   if (s == null) return null;
   var buf = new StringBuilder(s);
   while (buf.Length > 0 && Char.IsWhiteSpace(buf[0])) {
      buf.Remove(0, 1);
   }
   return buf.ToString();
}

These next two are a real annoyance. For whatever reason, Microsoft completely forgot case-sensitivity which is a huge concern when dealing with strings.

Contains - Not sure why you can't check whether a string contains another string with a case-insensitive compare. Here's a simple one-liner to resolve that:
public static bool Contains(this string s, string value, StringComparison comparisonType) {
   return (s.IndexOf(value, comparisonType) >= 0);
}

Replace - You have to get into the RegEx libraries to deal with string replaces that are case-insensitive, which is ludicrous. Here's an overload for Replace() that resolves this huge oversight:

public static string Replace(this string s, string oldValue, string newValue, StringComparison comparisonType) {
   if (s == null) return null;
   if (string.IsNullOrEmpty(oldValue) || newValue == null) return s;
   int idxNext = s.IndexOf(oldValue, comparisonType);
   if (idxNext < 0) return s;

   var result = new StringBuilder();
   int lenOldValue = oldValue.Length;
   int curPosition = 0;

   while (idxNext >= 0) {
      result.Append(s, curPosition, idxNext - curPosition);
      result.Append(newValue);
      curPosition = idxNext + lenOldValue;
      idxNext = s.IndexOf(oldValue, curPosition, comparisonType);
   }

   result.Append(s, curPosition, s.Length - curPosition);
   return result.ToString();
}

Every development shop has their own extension method library, and certainly I have plenty of those. But, arguably, those don't belong in the framework itself. These, however, are interesting in that Microsoft already put them in there for us, but they failed to hit a few very common needs served by these simple overloads. Enjoy!