A friend/colleague of mine, George, made an interesting assertion - that he prefers passing implementations, not representations. As we talked and unpacked what he meant by this, I discovered a deceptively simple idea that can make our code significantly more flexible and adaptable.

We can look at the distinction by comparing two examples from the .NET Framework.

When you want to compare two strings, you can (and should!) be explicit about what kind of string comparison you want. In our example here, we’re checking to see whether the current object has a specified name, and we don’t care about letter case.

public bool HasName(string name)
    return string.Equals(Name, name, StringComparison.OrdinalIgnoreCase);

Here we’ve passed the enumeration value StringComparison.OrdinalIgnoreCase to represent the kind of comparison we want - internally, the .Equals() method makes a decision based on that value and delivers the comparison we want.

Now consider what happens when we define a dictionary to map from names to people and we don’t care about letter case:

var map = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);

Carefully note that this time we’re using a StringComparer, not a StringComparison. We’re not passing an enumeration value; instead, we’re passing an actual implementation that will do the appropriate comparison for us.

What’s the difference?

With String.Equals(), we can only pass one of the predefined values that have already been made available:

public enum StringComparison
    CurrentCulture = 0,
    CurrentCultureIgnoreCase = 1,
    InvarientCulture = 2,
    InvarientCultureIgnoreCase = 3,
    Ordinal = 4,
    OrdinalIgnoreCase = 5

Contrast that with the Dictionary constructor where we could pass any implementation at all:

public Dictionary(IEqualityComparer<TKey> comparer)

The class StringComparer provides easy access to a number of standard implementations that we can easily reuse:

public abstract class StringComparer : IEqualityComparer<string>
    public static StringComparer InvariantCulture { get; }
    public static StringComparer InvariantCultureIgnoreCase { get; }
    public static StringComparer CurrentCulture { get; }
    public static StringComparer CurrentCultureIgnoreCase { get; }
    public static StringComparer Ordinal { get; }
    public static StringComparer OrdinalIgnoreCase { get; }

The key point to note here is that we’re not constrained to only use the choices that are pre-baked.

If we wanted our map to ignore whitespace and punctuation, we could create our own comparison class implementing IEqualityComparer<string> and use that instead.

At the edges of our systems, where we interface with other components or with users, we need to use representations because they serialize to names and other forms that cross those boundaries easily.

But within our systems, passing around the desired implementation instead is a very easy way to make our systems more flexible and more adaptable.

As long as we take the time to ensure that easy things are still easy to achieve - as the .NET Framework does by including the StringComparer class - we can do this with very low cost.


blog comments powered by Disqus
Next Post
Sharpen The Saw #15  16 Oct 2017
Prior Post
Sharpen The Saw #14  09 Oct 2017
Related Posts
Error Methods  25 Nov 2017
Avoiding the Singleton Pattern  22 Jul 2017
Implementing the Singleton Pattern  15 Jul 2017
Static Analysis tools for the Win  15 Apr 2017
Exception Logging  19 Jan 2016
On the Merits of Simple Code  28 Nov 2015
Semantic Types  27 Sep 2015
Command Line Processing  21 Sep 2014
Easy String Conversion  24 Aug 2014
Simpler code with DirectoryInfo  13 May 2014
More smart-code posts »
Related Pages
October 2017 archive