While writing a LINQ query, I really wanted to be able to combine instances of a string column. Of course the standard Sum methods are only designed for the numeric types. This was the prompt I finally needed to try my hand at writing an extension method.
So let's start with an easy example.
using System;
using System.Text;
using System.Collections.Generic;
using System.Data;
using System.Linq;
namespace CunningPlan
{
/// <summary>
/// Extension methods.
/// </summary>
public static class ExtensionLibrary
{
/// <summary>
/// Returns the left most characters from a string.
/// If the requested length is longer than the source string,
/// or if it is less than zero,
/// then the source string is returned unchanged.
/// </summary>
/// <param name="source">The original string</param>
/// <param name="length">The number of characters required.</param>
/// <returns>The 'N' left most characters from the source string.</returns>
public static string Left(this string source, int length)
{
return (length <= source.Length && length > 0)
? source.Substring(0, length) : source;
}
/// <summary>
/// Tests the supplied string to see if it can be
/// parsed as an integer
/// </summary>
/// <param name="source">String to check</param>
/// <returns>True if an integer value.</returns>
public static bool IsInteger(this string source)
{
int result = 0;
return int.TryParse(source, out result);
}
}
}
Things to note are that the containing class, and the methods have to be static. Next notice how the this keyword is used, the source parameters in the above two examples give the methods a handle on the object instance upon which they will be executed. The use of the this keyword in this context, is akin to when writing a class indexer. You can now write code like:
List<string> counties = new List<string>()
{ "Berkshire", "Hampshire", "Surrey" };
string countySum = counties.Sum();
We now have the means to add methods to any classes we like, even sealed ones. That's easy enough, so how do I now extend LINQ to have a Sum() method for strings? Well, all we need to do is follow the same approach as before. This time the source object will be defined as an IEnumerable string collection. The first example below takes the supplied collection, and builds a single concatenated string, using the specified delimiter. The second simple overloaded method, gives us a csv answer.
/// <summary>
/// Returns a string summation/concatentaion.
/// Where the source is empty, a blank string is returned.
/// </summary>
/// <param name="source">The set of strings to be 'summed'.</param>
/// <param name="delimiter">The delimiter to be used.</param>
/// <returns>The combined string, otherwise empty.</returns>
public static string Sum(this IEnumerable<string> source, string delimiter)
{
if (source == null) return "";
StringBuilder sb = new StringBuilder();
int count = source.Count();
int i = 0;
foreach (string s in source)
{
sb.Append(s);
if (++i < count) sb.Append(delimiter);
}
return sb.ToString();
}
/// <summary>
/// Returns a string summation/concatentaion using a default comma seperator.
/// If source is empty then a blank string is the result
/// </summary>
/// <param name="source">The set of strings to be 'summed'.</param>
/// <returns>The combined string, otherwise empty.</returns>
public static string Sum(this IEnumerable<string> source)
{
return Sum(source, ", ");
}
With these methods added to the class, we can now produce summations of string collections. For example:
List<string> counties = new List<string>()
{ "Berkshire", "Hampshire", "Surrey" };
string countySum = counties.Sum();
But I wanted to incorporate my method in LINQ, so here's a simple example of that:
NorthwindDataContext db = new NorthwindDataContext();
string londonNames = (from c in db.Customers
where c.City == "London"
select c.CompanyName).Sum();
The answer returned being:
"Around the Horn, B's Beverages, Consolidated Holdings, Eastern Connection, North/South, Seven Seas Imports"