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:
int count = input.ParsedValueOrDefault();
Let's consider also the equivalent code that uses the TryParse
pattern:
int count;
int.TryParse(input, out count);
Comparing the two examples:
|
ParsedValueOrDefault |
TryParse |
Lines of code |
1 |
2 |
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:
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 string
s, I developed methods that help ensure that string
s 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:
Signature |
Description |
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
:
int? countOrNull = input.ConvertToNullable();
if (countOrNull.HasValue)
{
int count = countOrNull.Value;
... }
int count;
if (int.TryParse(input, out 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