31 May 2010

JavaScript-style Substring in C#

One thing that really bugs me when writing code is having to use unnecessary extra constructs to avoid exceptions or useless default values emerging. One such situation is trimming a string to a particular length e.g.

string sentence = "The quick brown fox jumps over the lazy dog";
string firstFifty = sentence.Substring(0, 50);

I want the first 50 characters from the sentence but in this example we get an ArgumentOutOfRangeException because there aren’t 50 characters in sentence. Not too helpful and it's an easy mistake to make. To avoid the exception we have to do this:

firstFifty = sentence.Length < 50 ? sentence : sentence.Substring(0, 50);

Yikes! That’s a lot of extra rubbish when all I want is the equivalent of LEFT(sentence, 50) in SQL.

We can easily wrap this up in a "Left" method but chances are we’re going to need a “Right” too so instead we can go down the route JavaScript takes with its "slice" function. JavaScript’s string slice can take one integer argument which, if positive, returns characters from the start of the string and, if negative, returns characters from the end of the string. Adding an overload to allow it to take a padding character is probably sensible too. The end result looks like this:

firstFifty = sentence.Slice(50);
// "The quick brown fox jumps over the lazy dog"
	
string firstTen = sentence.Slice(10);
// "The quick "

string lastTen = sentence.Slice(-10);
// "e lazy dog"

firstFifty = sentence.Slice(50, '=');
// "The quick brown fox jumps over the lazy dog=============="

string lastFifty = sentence.Slice(-50, '=');
// "==============The quick brown fox jumps over the lazy dog"

A lot more concise and quite useful.

public static class StringExtensions
{
   /// <summary>
   /// Returns a portion of the String value. If value has Length longer than 
   /// maxLength then it is trimmed otherwise value is simply returned.
   /// </summary>
   /// <returns>
   /// String whose Length will be at most equal to maxLength.
   /// </returns>
   public static string Slice(this string value, int maxLength)
   {
      if (value == null) throw new ArgumentNullException("value");
      
      int start = 0;
      if (maxLength < 0)
      {
         start = value.Length + maxLength;
         maxLength = Math.Abs(maxLength);
      }
      return value.Length < maxLength ? value : value.Substring(start, maxLength);
   }
   
   /// <summary>
   /// Returns a portion of the String value. If value has Length longer than 
   /// length then it is trimmed otherwise value is padded to length with 
   /// shortfallPaddingChar.
   /// </summary>
   /// <returns>
   /// String whose Length will be equal to length.
   /// </returns>
   public static string Slice(this string value, int length, char shortfallPaddingChar)
   {
      if (value == null) throw new ArgumentNullException("value");
      
      string part = value.Slice(length);
      int abslen = Math.Abs(length);
      if(abslen > part.Length)
      {
         part = length < 0 ? part.PadLeft(abslen, shortfallPaddingChar) : part.PadRight(abslen, shortfallPaddingChar);
      }
      return part;
   }
}

No comments: