string.KeepOnly

Often when working with string data you need to strip away certain characters, filtering out some kernel that you’re interested in. For example, you may want to compare two phone numbers to see if they contain the same digits but you don’t care if the formatting is different. Or you may need to strip punctuation, or keep only letters, etc.

Normally when you encounter a situation like this, we pull out a for loop and get the job done. But if you solve any one of the above scenarios directly with an off-the-cuff for loop, you’ve just missed your chance to solve the more generic problem of keeping only certain types of characters in a string. And in C#, this is a solution we can easily package into an extension method:

public static string KeepOnly(this string text, Func<char, bool> filter)
{
    if (text == null)
        return null;

    var sb = new StringBuilder();

    foreach (var c in text)
    {
        if (filter(c))
            sb.Append(c);
    }

    return sb.ToString();
}

Armed with this simple method, we can now strip a string of anything but digits like this:

var digits = "(123) 456-7890".KeepOnly(c => char.IsDigit(c));

Which can be packaged into an even more easy-to-read extension:

public static string KeepOnlyDigits(this string text)
{
    return text.KeepOnly(c => char.IsDigit(c));
}

Which can be used like this:

var digits = "(123) 456-7890".KeepOnlyDigits();

Which, I guarantee you, is a thousand times more readable and reusable than an inline for loop.

In my .NET utility library I’ve defined a bunch of methods like this for common string filtering. Here’s the entire class:

using System;
using System.Text;
using System.Linq;

namespace Cooper.Extensions
{
    public static class StringKeepOnlyExtensions
    {
        public static string KeepOnly(this string text, Func<char, bool> filter)
        {
            if (text.IsNull())
                return null;

            var sb = new StringBuilder();

            foreach (var c in text)
            {
                if (filter(c))
                    sb.Append(c);
            }

            return sb.ToString();
        }

        public static string KeepOnly(this string text, char characterToKeep)
        {
            return text.KeepOnly(c => c == characterToKeep);
        }

        public static string KeepOnly(this string text, char[] charactersToKeep)
        {
            return text.KeepOnly(c => charactersToKeep.Contains(c));
        }

        public static string KeepOnlyDigits(this string text)
        {
            return text.KeepOnly(c => c.IsDigit());
        }

        public static string KeepOnlyDigitsAnd(this string text, char additionalCharacterToKeep)
        {
            return text.KeepOnly(c => (char.IsDigit(c) || c == additionalCharacterToKeep));
        }

        public static string KeepOnlyDigitsAnd(this string text, char[] additionalCharactersToKeep)
        {
            return text.KeepOnly(c => (char.IsDigit(c) || additionalCharactersToKeep.Contains(c)));
        }

        public static string KeepOnlyLetters(this string text)
        {
            return text.KeepOnly(c => char.IsLetter(c));
        }

        public static string KeepOnlyLettersAnd(this string text, char additionalCharacterToKeep)
        {
            return text.KeepOnly(c => (char.IsLetter(c) || c == additionalCharacterToKeep));
        }

        public static string KeepOnlyLettersAnd(this string text, char[] additionalCharactersToKeep)
        {
            return text.KeepOnly(c => (char.IsLetter(c) || additionalCharactersToKeep.Contains(c)));
        }

        public static string KeepOnlyLettersAndDigits(this string text)
        {
            return text.KeepOnly(c => char.IsLetterOrDigit(c));
        }

        public static string KeepOnlyLettersAndDigitsAnd(this string text, char additionalCharacterToKeep)
        {
            return text.KeepOnly(c => (char.IsLetterOrDigit(c) || c == additionalCharacterToKeep));
        }

        public static string KeepOnlyLettersAndDigitsAnd(this string text, char[] additionalCharactersToKeep)
        {
            return text.KeepOnly(c => (char.IsLetterOrDigit(c) || additionalCharactersToKeep.Contains(c)));
        }

        public static string KeepOnlySpaces(this string text)
        {
            return text.KeepOnly(c => (c == ' '));
        }

        public static string KeepOnlyWhitespace(this string text)
        {
            return text.KeepOnly(c => (c.IsWhitespaceCharacter()));
        }
    }
}


Your Mindset is More Important

It’s amazing how prone we can be to superficial measures of maturity. Sometimes we get it in our head—though we wouldn’t ever say it out loud—that using things like TDD, BDD, IoC, ORM, etc. will automatically lead us to software quality nirvana. It doesn’t.

Kyle Baley has a great article reminding me that, ultimately, what makes a software team great is not the tools or practices that they use, but their mindset. It is only when developers care deeply about keeping the codebase clean and maintainable and producing quality results that these tools and practices show their merit. In order to build truly great software we need to do more than use great tools and say we follow great practices; we need to cultivate and encourage a culture of quality.