Click here to Skip to main content
15,891,867 members
Articles / Programming Languages / C#
Article

A Few Extension Methods that Streamline Working with strings and Data Stores

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
16 Jan 2012CPOL6 min read 16.7K   142   9   2
These are a few extension methods that I have written to simplify working with strings and data stores, and within that context, to make my code more readable and expressive

Introduction

When I started writing a lot of data service code and working extensively with Linq-to-SQL, I found a need to simplify my code by creating wrappers for code snippets that I found awkward in their most basic and direct forms. In this article, I share a few of those enhancements that others may find useful.

Background

There are good reasons for using the TryParse pattern (described here). (I'm referring to "using" it in the sense of calling the TryParse method on .NET Framework types, and possibly other types.) Using TryParse enables us to avoid using the "try/parse/catch" anti-pattern. (Notice that I am not calling try/catch an anti-pattern!) Still, one might wish that the TryParse pattern's two outputs could be reduced to just one by simply returning a default value if the input string is null, empty or in an invalid format. This simplification will work in many situations. Here's an example:

C#
// Where input is a string...
int count = input.ParsedValueOrDefault();

Let's consider also the equivalent code that uses the TryParse pattern:

C#
int count;
int.TryParse(input, out count);    

Comparing the two examples:

ParsedValueOrDefault TryParse
Lines of code 12
Characters 38 (not counting whitespace)38 (not counting whitespace)
Good input count is the value parsed from the input string. count is the value parsed from the input string.
Bad input count == 0 count == 0

It's easy to see that the examples are functionally equivalent and that it's better to use ParsedValueOrDefault in situations where a single line of code is preferred - for example, when using Linq-to-SQL to retrieve data from a data source and shaping the output, as in this code:

C#
// Get from the data store all employees who have been 
// with the company for more than 2 years.
resultList =
        for item in Employees
        where DateOfHire < DateTime.Now.AddYears(-2)
        select new Employee
        {
          Salary = Salary.ParsedValueOrDefault(),
          ...
        }).ToList();

Granted, it would be preferable NOT to store a currency value as a string - but one cannot always control the format in which data is stored.

In addition to finding a more streamlined way, that is useful in many situations, to deal with primitive- and struct- (e.g. DateTime or TimeSpan) type values represented as strings, I developed methods that help ensure that strings returned to a service client are never null, even if they are kept in a data store as null. (The consensus among my teammates has been that this helps prevent errors and enables some streamlining of code. The downside is that care must be taken to ensure the desired condition.) And of course, that conversion must be reversible. Thus, the methods EmptyIfNull, EmptyIfNullTrimmed, NullIfEmpty and NullIfEmptyTrimmed methods came into being. Finally, to complement the .NET Framework's GetValueOrDefault method, I needed a convenient way to transform a default value to null for storage, and the NullIfDefault method solves that problem.

All of the methods other than ParsedValueOrDefault are extremely simple - nothing more than wrappers for conditional expressions. To me, it is more than worthwhile to write once and frequently call a well-named method instead of frequently writing a (relatively more cryptic) conditional expression.

Using the Code

The downloadable sample code contains all of the methods summarized below, with documentation for each method plus test code that exercises each method.

The following table presents an overview:

SignatureDescription
T ParsedValueOrDefault<T>(this string subject, params object[] optionalArguments) Converts a string to a value of type T using the TryParse method. Information about the input string (the direct return value of TryParse) is discarded and default(T) is returned if the input string was null, empty or invalid. Works only for value types that implement the TryParse pattern - i.e. bool, byte, char, DateTime, DateTimeOffset, decimal, double, float, int, long, sbyte, short, TimeSpan, uint, ulong and ushort.

optionalArguments are additional arguments that may be valid and useful depending on the type parameter.
  • For numeric types: (IFormatProvider formatProvider, NumberStyles numberStyles = NumberStyles.None);
  • For date/time types (including TimeSpan in .NET 4.0): (IFormatProvider formatProvider, DateTimeStyles dateTimeStyles = DateTimeStyles.None).
The ...styles argument is optional (and not used with TimeSpan, in any case), and if both arguments are supplied, the order in which they're supplied does not matter. The argument(s) will be applied only if formatProvider is supplied and non-null.
string EmptyIfNull(this string subject) Gets the specified string, converted to string.Empty if it is null.
string EmptyIfNullTrimmed(this string subject) Gets the specified string, trimmed if not null or empty, converted to string.Empty if it is null.
Nullable<T> NullIfDefault<T>(this T subject) where T : struct Converts a value type argument to a nullable value type result. If the argument equals the default value for its type, the result is null. (The implementation of this method has been completed only for selected types: bool, byte, char and DateTime.)
string NullIfEmpty(this string subject) Returns a string that is null if the argument string is null or empty; otherwise, the argument string.
string NullIfEmptyTrimmed(this string subject) Returns a string that is null if the argument string is null or empty; otherwise, the argument string trimmed.

Points of Interest

When examining the downloadable code, the reader will notice that ParsedValueOrDefault wraps another method, ConvertToNullable. Initially, and for a long time, I was happy to write ConvertToNullable().GetValueOrDefault() in my code. I had thought it wise to design ConvertToNullable to provide all of the functionality of TryParse - including the ability to determine from the output whether the input string had actually been a valid representation of a value of type T. However, I avoided using ConvertToNullable apart from GetValueOrDefault. Why? Let's compare using ConvertToNullable vs. TryParse when we want to preserve and use information about the input string:

C#
int? countOrNull = input.ConvertToNullable();
if (countOrNull.HasValue)
{
    int count = countOrNull.Value;
    ... // Do something with count.
}    

int count;
if (int.TryParse(input, out count))
{
    ... // Do something with count.
}    

Clearly, in this situation TryParse is preferable, and if one insisted on using ConvertToNullable, one would be falling into an anti-pattern! Since it wouldn't make sense to use ConvertToNullable in this way, there is really no reason to continue using a method that preserves the information (whether the input string be successfully parsed) that is the direct return value of TryParse. Thus, ParsedValueOrDefault was born, and I plan to call it henceforth instead of using the more cumbersome ConvertToNullable().GetValueOrDefault() calls.

The next step will be to refactor ParsedValueOrDefault for performance by removing its dependence on ConvertToNullable, as it is now clear that there' no particular reason why Nullable<T> instances ever should come into play. However, for better or worse, as long as my legacy code continues to use ConvertToNullable, it must be maintained as a public method in my library. In the downloadable code, I have left ConvertToNullable public and have decorated it with an ObsoleteAttribute instance, which reflects its status in my library. If you want to actually use these methods, I recommend that you remove the attribute and make ConvertToNullable non-public.

These insights about ConvertToNullable crystallized for me only when I decided to write this article - which "for the umpteenth time" reinforces a point that I learned early in my career: Documenting and explaining code in natural language is extremely valuable - I would argue: essential, when time can be found to do it - step in the development of high-quality code, because it tends to expose important facts that aren't necessarily obvious while a developer is in "thinking in code" mode.

History

  • 16th January, 2012: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Concur
United States United States
George Henry has worked as a software developer for more than 20 years. He is currently employed by Concur in Bellevue, Washington, USA.

Comments and Discussions

 
QuestionNice! Pin
RAND 45586616-Jan-12 6:34
RAND 45586616-Jan-12 6:34 
AnswerRe: Nice! Pin
George Henry 195416-Jan-12 7:21
George Henry 195416-Jan-12 7:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.