Friday 2 January 2009

IEnumerable Extension Methods

After looking over some questions on StackOverflow it got me thinking about extension methods. It's not really something which I have looked into much as I only use Visual Studio 2008 at home for personal projects. I stumbled upon one post about potential extension methods to add to a colaboration on CodePlex for everyones use. I was scrolling through some of the potentials to be submitted and came across this post. It intrigued me as the main method was converting an IEnumerable to a string with a separator ... so would convert an array of ints 1,2,3 into the string "1, 2, 3" if the separator was ", " ... I think you get the point.

However the way they were doing it was the usual "go through all the items, add the separator after everyone, then at the end take the last off". Personally when I see this I get a little sad. It's just not very elegant but also it means you don't harness the power of the framework ... which is what it is there for!

Side Note With the advent of .net 2.0 Generics was brought in. This opened up a whole world of fun for developers and a lot of useful things especially around the area of collections. If you've not looked at Generics I would highly recommend working your Google-Fu and doing some reading.

Anyway ... using generics I decided to refactor the ToString method for IEnumerable to use ToArray() of a List and string.join(). This might be a new way for some people of concatenating values together with a supplied separator. It leverages the power of the .net framework to do the work and avoids having to add an extra separator and then removing it once you have iterated through all the items in the list and performing the specified delegate function.

        public static string ToString<T>(this IEnumerable<T> collection,
Func<T, string> stringElement, string separator)
        {
            List<string> result = new List<string>();
            foreach (T item in collection)
            {
                result.Add(stringElement(item));
            }
            return string.Join(separator, result.ToArray());
        }


So moving on from this I thought that a List has a ForEach extension method which executes a delegate on each of the elements in the list; but not on an IEnumerable. This has been discussed and documented in so many places I'm not going to write about it here.  So what I wanted to do was break out the ForEach functionality out of the ToString implementation so it can be used injunction with it, but also by itself.


        public static IEnumerable<T> ExecuteForEach<T>(this IEnumerable<T> collection,
Func<T, T> function)
        {
            List<T> result = new List<T>();
            foreach (T item in collection)
            {
                result.Add(function(item));
            }
            return result.AsEnumerable();
        }


I made it return an IEnumerable<T> so it could be piped together to keep with the design of linq and lambda expressions etc. The limitation of this implementation is that it returns the same type as the type passed into the Func so you can't change the type, such as converting int to string. So take two came along ...


        public static IEnumerable<U> ExecuteForEach<T, U>(this IEnumerable<T> collection,
Func<T, U> function)
        {
            List<U> result = new List<U>();
            foreach (T item in collection)
            {
                result.Add(function(item));
            }
            return result.AsEnumerable();
        }


Same as the previous example, but with two different generic types, this enables the conversion of int to string, but also performing actions on the same types and returning the updated values eg. increment a list of ints.

With using the updated ExecuteForEach<T, U> (I decided on calling it ExecuteForEach as it will update / perform a function on each of the items in the collection and update them, not just perform an function on them) we can now update the ToString method call to be just two lines;  it abstracts the looping away into a different function which can be used again for different actions, but also uses the power of the framework with the string.join functionality.


        public static string ToString<T>(this IEnumerable<T> collection,
Func<T, string> stringElement, string separator)
        {
            List<string> possible = collection
.ExecuteForEach(t => stringElement(t))
.ToList();
            
            return string.Join(separator, possible.ToArray());
        }


A simple usage of this is ...


        public static string ToString<T>(this IEnumerable<T> collection, string separator)
        {
            return ToString(collection, t => t.ToString(), separator);
        }


... and the unit test to go with it ...


        [TestMethod]
        public void ToStringTest()
        {
            var ints = new int[] { 1, 2, 3 };
            var result = ints.ToString(", ");
            Assert.AreEqual("1, 2, 3", result);
        }


Hope this all makes sense, please let me know if you have any comments or thoughts about this :-)

No comments: