Thursday 23 September 2010

C# Left() and Right() String Extensions

Classic ASP developers will recognise these two handy methods for grabbing a specific number of characters from the start or end of a string.  They've proven to be quite useful to me in the past with my VBScript development.

I decided the best way to use them in future would be to convert them to string extension methods in C#. This allows you to simply call the method on an existing string.

 
    namespace BaseFour.Extensions
    {
        public class StringExtensions
        {
            public static string Left(this string s, int characters)
            {
                if (characters < 0)
                    throw new ArgumentOutOfRangeException();
                if (characters > s.Length)
                    characters = s.Length;
                return s.Substring(0, characters);
            }
    
            public static string Right(this string s, int characters)
            {
                if (characters < 0)
                    throw new ArgumentOutOfRangeException();
                if (characters > s.Length)
                    characters = s.Length;
                return s.Substring(s.Length - characters, characters);
            }
        }
    }
 

Usage:

 
    string value = "SuperDuper";

    value.Left(5); // Super
    value.Right(5); // Duper
 

They are great for doing some simple trailing slash checking for directories and urls before processing.

C# Overcoming Path.Combine() Issues

Working with files can be quite fun in ASP.NET, especially when some of the tools provided by the framework don't always work as expected.  I came across an issue recently with System.IO.Path.Combine() that had me scratching my head for quite a while. I've used this method previously without issue but this particular case was cause for concern.  For instance, the following code:

 
    System.IO.Path.Combine("c:\root\", "\directory\file.txt");
 

Returns the path: \directory\file.txt

I was hoping for something more along the lines of: c:\root\directory\file.txt

I'm not sure why it doesn't work as intended.  After doing a few quick Google searches I found out that I wasn't alone in my troubles with this.  It turns out that a lot of people are a little upset with the lack of functionality with this particular method.  This blog in particular helps to highlight some of the unusual concatenation attempts when combining two relatively simple paths.

Some examples of this (duplicated from the thingsihateaboutmicrosoft blog):

ExamplePath1Path2Result
Ac:\path\dir\file.txtc:\path\dir\file.txt
Bc:\path\\dir\file.txt\dir\file.txt
Cc:\pathdir\file.txtc:\path\dir\file.txt
Dc:\path\dir\file.txt\dir\file.txt
Ec:dir\file.txtc:dir\file.txt
Fc:\dir\file.txt\dir\file.txt

You can see that in B, D, E and F we don't get the response we would expect.  In fact only two of these provide a result that would pass as valid.  We shouldn't really have to do lots of string manipulation (i.e. removing or appending leading and trailing slashes) before passing our paths to Path.Combine() because that's what it's for, right?

After realising that I didn't want to have to do extra string manipulation for every path I ever pass to Path.Combine() I decided to write my own wrapper method that would do some of the hard work for me, so here it is:

 
    namespace BaseFour.IO
    {
        public static class Path
        {
            public static string Combine(string path1, string path2)
            {
                // Ensure neither end of path1 or beginning of path2 have slashes
                path1 = path1.Trim().TrimEnd(System.IO.Path.DirectorySeparatorChar);
                path2 = path2.Trim().TrimStart(System.IO.Path.DirectorySeparatorChar);
        
                // Handle drive letters
                if (path1.Substring(path1.Length - 1, 1) == ":")
                    path1 += System.IO.Path.DirectorySeparatorChar;
        
                return System.IO.Path.Combine(path1, path2);
            }
        }
    }
 
Use it in exactly the same way as the System.IO.Path.Combine() method, but remember to put it in it's own namespace first or it is likely to clash if you're referencing System.IO in the same class.

And some test results:

ExamplePath1Path2Result
Ac:\path\dir\file.txtc:\path\dir\file.txt
Bc:\path\\dir\file.txtc:\path\dir\file.txt
Cc:\pathdir\file.txtc:\path\dir\file.txt
Dc:\path\dir\file.txtc:\path\dir\file.txt
Ec:dir\file.txtc:\path\dir\file.txt
Fc:\dir\file.txtc:\path\dir\file.txt


Works nicely.  The code still uses the Path.Combine() method but it ensures that it'll always be in a format that Path.Combine() expects.