Saturday, May 22, 2010

Substring Extension Method that does not give exception

One of the limitations of the .Net Substring(start,[length]) method is it expects the arguments to be within range otherwise we get ArgumentOutOfRange. This is by design as strings are immutable, a member function doesn't exist. String manipulation is one of the most common activity, and it becomes huge annoyance when your truncate or some other method fails at runtime. You may have to write additional code to guard against range "overreach".

Here is a wonderful extension method (IMHO) that solves your substring overreach issue once for all -- instead of runtime exception, it instead returns the most logical substring based on parameters and substitutes nulls for anything beyond that range -- including an empty string if the whole thing is beyond the range.

Substring(startIndex, handleIndexException)
and Substring(startIndex, length, handleIndexException)

"startIndex" and "length" work just like the original substring. But with handleIndexException enabled(set to TRUE) is where the behavior gets interesting:


  • "startIndex" can be negative or can exceed length of string.
  • "length": when negative is interpreted as take characters from the left. Therefore, Substring(2,-1,true) means start from third position (remember C# count is zero-based), and take one character from the left. See extensive examples below.

    Note, I created another extension method to show nulls. Also I tested it with all possible boundary conditions. Attached are the test results.

         /// 
         /// String Extension for Substring with startIndex, that intelligently handles out of bounds without returning exception
         /// see http://jagdale.blogspot.com/2010/05/substring-extension-method-that-does.html
         /// 
         /// 
         /// 
         /// 
         /// if set to "true" handles intelligently handles the exception, otherwise works like regular Substring
         public static String Substring(this String val, int startIndex, bool handleIndexException)
         {
             if (handleIndexException)
             {
                 if (string.IsNullOrEmpty(val))
                 {
                     return val;
                 }
                 int instrlength = val.Length;
                 return val.Substring(startIndex < 0 ? 0 : startIndex > (instrlength - 1) ? instrlength : startIndex);
             }
             // ELSE handleIndexException is false so call the base method
             return val.Substring(startIndex);
         }
    
    
    
    /// /// String Extension for Substring with startIndex and Length, that intelligently handles out of bounds without returning exception /// see http://jagdale.blogspot.com/2010/05/substring-extension-method-that-does.html /// /// /// /// /// /// if set to "true" handles intelligently handles the exception, otherwise works like regular Substring public static String Substring(this String val, int startIndex, int length, bool handleIndexException) { if (handleIndexException) { if (string.IsNullOrEmpty(val)) { return val; } int newfrom, newlth, instrlength = val.Length; if (length < 0) //length is negative { newfrom = startIndex + length; newlth = -1 * length; } else //length is positive { newfrom = startIndex; newlth = length; } if (newfrom + newlth < 0 || newfrom > instrlength - 1) { return string.Empty; } if (newfrom < 0) { newlth = newfrom + newlth; newfrom = 0; } return val.Substring(newfrom, Math.Min(newlth, instrlength - newfrom)); } // ELSE handleIndexException is false so call the base method return val.Substring(startIndex, length); } //TEST IT: //Console.WriteLine("-1,3 : " + "abcde".Substring(-1, 3, true).ShowNull()); //....etc... RESULTS from a suite of tests for the string "abcde":
    start,length:result
    ===================
    -1 : abcde
    -0 : abcde
    2 : cde
    4 : e
    5 : <<null>>
    12 : <<null>>
    -1,-1: <<null>>
    -1, 0: <<null>>
    -1, 1: <<null>>
    -1, 3: ab
    -1, 9: abcde
    0,-1: <<null>>
    0,-0: <<null>>
    0, 3: abc
    0, 9: abcde
    2,-3: ab
    2,-2: ab
    2,-1: b
    2,-0: <<null>>
    2, 2: cd
    2, 6: cde
    4,-9: abcd
    4,-4: abcd
    4,-0: <<null>>
    4, 1: e
    4, 4: e
    5,-9: abcde
    5,-5: abcde
    5,-4: bcde
    5,-0: <<null>>
    5, 1: <<null>>
    I hope this helps. I spent several hours first browsing the web, then writing/testing the code. This can be a great time saver!
  • 2 comments:

    Dave said...

    Nce Vijay! I was especially glad to see your extension method didn't just catch the exception and do nothing :)

    Vijay Jagdale said...

    Thanks David. I just updated the post. (there are always ways to tweak the code). Adding "padding" functionality might be useful as well)