Thursday, September 13, 2012

C# coolness - extension methods

This is a feature that I fell in love with in Groovy - the ability to add functionality to existing classes. This dynamic capability can really reduce your code footprint significantly with only moderate complexity!  I recently had a use case where I wanted to be able to split a string based on spaces (this was for command line parsing).  With a little assist from some code found on StackOverflow and this functionality, I had a quick and useful extension I could use in parsing out the command line.  This could still use a little work, but is pretty functional for most basic cases.

First - attribution.  The split code in this post was inspired by this response to a question on StackOverflow.

In order to create extension methods, you have to define your class as a public static class.  Extension methods are static methods and the first parameter is the type you are extending - here is the syntax: "this typeExtending varName".  This first snippet sets up a new Split method that takes in a delegate for how it should act on each character.

public static IEnumerable<string> Split(this string text, 
    Func<char, bool> internalFunction)
{
    int next = 0;
    for (int chr = 0; chr < text.Length; chr++)
    {
        if (internalFunction(text[chr]))
        {
            yield return text.Substring(next, 
                      chr - next).CleanString();
            next = chr + 1;
        }
        // process the last char differently b/c IF it 
        // is a quote, it will get left out (since 
        // internalFunction will return false since it 
        // never reaches the split character - unless a 
        // trailing space is added).
        else if (chr == text.Length - 1)
        {
            yield return text.Substring(next).CleanString();
        }
    }
}

Here is the implementation for the CleanString method (used in the above snippet).  Notice that this is also an extension method:

public static string CleanString(this string str)
{
    string returnStr = str;
    if (!String.IsNullOrEmpty(str))
    {
        returnStr = returnStr.Trim();
        returnStr = returnStr.TrimMatchingQuotes();
    }
    return returnStr;
}

Here is the implementation for the TrimMatchingQuotes (used in the above snippet).  This is again an extension method:


public static string TrimMatchingQuotes(this string input)
{
    // strings less than 2 chars don't have matching quotes 
    if ((input.Length >= 2) &&
        (input[0] == QUOTE) && 
        (input[input.Length - 1] == QUOTE))
    {
        return input.Substring(1, input.Length - 2);
    }
    return input;
}


Now for the final piece of goodness!  For splitting command lines that honor quotes (e.g. executable -t "some text") - please note, this does not work with quotes inside your quotes - I leave that to you if you need it!


public static IEnumerable 
    SplitStringHonorQuotes(this string str, char splitChar)
{
    bool inQuotes = false;
    return str.Split(character =>
                {
                    if (character == QUOTE)
                    {
                        // if we're in quotes, now we aren't
                        inQuotes = !inQuotes;
                    }
                    // don't split if inside a quoted section
                    return !inQuotes && character == splitChar;
                });
}


That's it, now you have some extension methods to help split your command line.  Drop all that code in a class like this:
 

using System;
using System.Collections.Generic;

namespace ProjectNamespace
{
    public static class StringUtils
    {
        // This constant is used in above code
        private const char QUOTE = '\"';
        // add above code here...
    }
}

Now, to use it:

// import your StringUtils class with extension methods
using ProjectNamespace;
...
string[] cmdArgs = 
   "-C command -t \"some text\"".SplitStringHonorQuotes(' ').ToArray();


I hope you found this post useful! Feel free to use this code as you wish... if you need a license, you can use the Apache license.  A link back to this post, while not required, would be appreciated.

Finally, I welcome constructive criticism on how this can be improved.  I am new to the .Net world, so I am still learning what C# has to offer.

2 comments:

  1. Go has this type of attribution built into the language. You can add new methods to classes, etc, because the methods (or functions) are not bound to the body of the class or a particular file (though I think you can if you need to).

    However, it seems to me that sometimes people want to add far more features to a module, when really they should be building a module independent of the other. Adding too much to a module (or class) overburdens the design like crazy. I can't stress enough how code becomes more spaghetti like and obtuse because it has grown beyond it's original intent.

    ReplyDelete
  2. This is a fair point... it is easy to get carried away when you can do something cool like this. In truth, what does it really buy you? I could not really say. In C# you are required to "use" (i.e. import) the utility file (at least its namespace) whereas in Groovy (if I recall correctly), you just used it without special imports (making it even more natural). However, I think this is a good use case for this functionality... although, it might be better to have simply overloaded the "split" method rather than renaming it to splitStringHonorQuotes(). When you think of needing to split, it is more natural to use the method on the "string" rather than using a special utility. I agree though, if you aren't careful, you get unusable spaghetti.

    ReplyDelete

Followers