Introduction
While writing a C# program, there are times when one needs to parse string
values to int
, bool
, long
, etc. or equivalent nullable types. During my short career as a C# programmer, I have noticed repetitive use of Int.TryParse
, bool.TryParse
, etc. functions in the code. This is not harmful, nonetheless, it reduces the productivity and requires lot of refactoring as project matures (and what about internationalization/localization?). Above all, I have seen devs asking how to parse as Nullable types.
Another fact, when several programmers work on a project, they use, as per their convenience, different means for parsing (e.g., using Convert
/struct
methods (Parse
and TryParse
)/IConvertible
methods) which impacts the code readability. Some well managed projects take the pain to create a separate project and reference it wherever needed, which is a good practice.
The idea behind this solution is to provide a way to group all string
conversion functions as an extension class which can easily support the localization needs with minimal efforts.
About Extension
The proposed extension class exposes only two publicly extended methods:
TryTo<T>
(T: struct, IConvertible
): Performs conversion to non-nullable struct
type. I have included support for Enum
parsing. ToNullable<T>
(T: struct, IConvertible
): Performs conversion to nullable struct
type. I have included support for Enum
parsing.
In fact, the design of this extension is not rocket science so I simply copy-pasted the method signature below:
public static bool TryTo<T>(this string value,
out T result,
string dateTimeFormat = "yyyy-MM-dd HH:mm:ss",
Format stringFormat = Format.UsEnglish)
where T : struct, IConvertible
public static Nullable<T> ToNullable<T>(this string value,
string dtForm = "yyyy-MM-dd HH:mm:ss",
Format format = Format.UsEnglish)
where T : struct, IConvertible
For keen eyes, the biggest difference in these methods signature is absence of out T result
parameter in ToNullable<T>
method. I omitted this out
parameter as I didn't find it convenient as by definition: "If something CANNOT be converted to type T
, then value of Nullable<T>
must be NULL
. Otherwise, the converted value." (Hope readers will agree.)
Another discombobulating parameter in these method signature is string dtForm = "yyyy-MM-dd HH:mm:ss"
. Why does this function require dateTime
format string? All I am going to do is TryTo<int>
. Actually, it was all the way possible to design overloaded functions for each type (and all the readers may choose to do so). However, I kept it that way and provided a default value, so that users do not need to worry about it for each conversion. Of course, when you do TryTo<DateTime>
, do not forget to provide a good value.
The final parameter format is an ENUM
, which maps to culture-info. Of course, I could directly ask for System.Globalization.CultureInfo
instance. But, I prefer that when program starts, the culture info value is read by the program from some file/db/config/UI (as programmer likes) as string
and the program calls cultureString.TryTo<Format>()
to convert to culture enum
values and then uses this value for other conversions. (Some great programmers will find, in my code, the use of dictionary/array too time consuming operations for such simple conversions, but, they know very well how to tune this program to their requirements).
Ok folks, too much blah blah from me... let's see the code...
The Code Itself
Without much talk, following is the code:
namespace StringTryTo
{
public enum Format
{
UsEnglish = 0,
FrFrench,
UkEnglish
}
public static class StringTryToExt
{
private delegate bool ConvertFunc<T>
(string value, string dtForm, IFormatProvider formProvider, out T result)
where T : struct, IConvertible;
private static readonly Dictionary<Type, Delegate> lookUp;
private static readonly CultureInfo[] iformatProvider;
static StringTryToExt()
{
iformatProvider = new[] { (new CultureInfo("en-US")),
(new CultureInfo("fr-FR")),
(new CultureInfo("en-GB")) };
<int>
lookUp = new Dictionary<Type, Delegate>
{
{typeof(sbyte), new ConvertFunc<sbyte>(TryTo)},
{typeof(short), new ConvertFunc<short>(TryTo)},
{typeof(int), new ConvertFunc<int>(TryTo)},
{typeof(long), new ConvertFunc<long>(TryTo)},
{typeof(byte), new ConvertFunc<byte>(TryTo)},
{typeof(ushort), new ConvertFunc<ushort>(TryTo)},
{typeof(uint), new ConvertFunc<uint>(TryTo)},
{typeof(ulong), new ConvertFunc<ulong>(TryTo)},
{typeof(float), new ConvertFunc<float>(TryTo)},
{typeof(double), new ConvertFunc<double>(TryTo)},
{typeof(decimal), new ConvertFunc<decimal>(TryTo)},
{typeof(char), new ConvertFunc<char>(TryTo)},
{typeof(bool), new ConvertFunc<bool>(TryTo)},
{typeof(DateTime), new ConvertFunc<DateTime>(TryTo)}
};
}
public static bool TryTo<T>(this string value,
out T result,
string dateTimeFormat = "yyyy-MM-dd HH:mm:ss",
Format stringFormat = Format.UsEnglish)
where T : struct, IConvertible
{
var typeOfT = typeof(T);
if (typeOfT.IsEnum)
{
return TryTo(value, out result);
}
else
{
return ((ConvertFunc<T>)lookUp[typeOfT])
(value, dateTimeFormat, iformatProvider[(int)stringFormat], out result);
}
}
public static Nullable<T> ToNullable<T>(this string value,
string dtForm = "yyyy-MM-dd HH:mm:ss",
Format format = Format.UsEnglish)
where T : struct, IConvertible
{
T result;
var typeOfT = typeof(T);
if (typeOfT.IsEnum)
{
return TryTo(value, out result) ? (new Nullable<T>(result)) : null;
}
else
{
return ((ConvertFunc<T>)lookUp[typeOfT])
(value, dtForm, iformatProvider[(int)format], out result) ?
(new Nullable<T>(result)) : null;
}
}
private static bool TryTo<T>(string value, out T result) where T : struct, IConvertible
{
return Enum.TryParse(value, out result);
}
private static bool TryTo
(string value, string dtForm, IFormatProvider formProvider, out DateTime result)
{
return DateTime.TryParseExact
(value, dtForm, formProvider, DateTimeStyles.None, out result);
}
private static bool TryTo(string value, string dtForm,
IFormatProvider formProvider, out bool result)
{
return bool.TryParse(value, out result);
}
private static bool TryTo(string value, string dtForm,
IFormatProvider formProvider, out char result)
{
return char.TryParse(value, out result);
}
private static bool TryTo(string value, string dtForm,
IFormatProvider formProvider, out decimal result)
{
return decimal.TryParse(value, System.Globalization.NumberStyles.Any,
formProvider, out result);
}
private static bool TryTo(string value, string dtForm, IFormatProvider formProvider,
out double result)
{
return double.TryParse
(value, System.Globalization.NumberStyles.Any, formProvider, out result);
}
private static bool TryTo
(string value, string dtForm, IFormatProvider formProvider, out float result)
{
return float.TryParse
(value, System.Globalization.NumberStyles.Any, formProvider, out result);
}
private static bool TryTo(string value, string dtForm,
IFormatProvider formProvider, out ulong result)
{
return ulong.TryParse(value, System.Globalization.NumberStyles.Any,
formProvider, out result);
}
private static bool TryTo(string value, string dtForm,
IFormatProvider formProvider, out uint result)
{
return uint.TryParse
(value, System.Globalization.NumberStyles.Any, formProvider, out result);
}
private static bool TryTo(string value, string dtForm,
IFormatProvider formProvider, out ushort result)
{
return ushort.TryParse(value, System.Globalization.NumberStyles.Any,
formProvider, out result);
}
private static bool TryTo(string value, string dtForm,
IFormatProvider formProvider, out byte result)
{
return byte.TryParse
(value, System.Globalization.NumberStyles.Any, formProvider, out result);
}
private static bool TryTo(string value, string dtForm,
IFormatProvider formProvider, out long result)
{
return long.TryParse
(value, System.Globalization.NumberStyles.Any, formProvider, out result);
}
private static bool TryTo(string value, string dtForm,
IFormatProvider formProvider, out int result)
{
return int.TryParse
(value, System.Globalization.NumberStyles.Any, formProvider, out result);
}
private static bool TryTo
(string value, string dtForm, IFormatProvider formProvider, out short result)
{
return short.TryParse(value,
System.Globalization.NumberStyles.Any, formProvider, out result);
}
private static bool TryTo(string value, string dtForm,
IFormatProvider formProvider, out sbyte result)
{
return sbyte.TryParse(value,
System.Globalization.NumberStyles.Any, formProvider, out result);
}
}
}
Using the Code
Following code snippet shows conversion examples:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace StringTryTo
{
public enum Test
{
Test1,
Test2
}
class Program
{
static void Main(string[] args)
{
int i;
long l;
short s;
Console.WriteLine("1 000 000".TryTo<int>(out i, stringFormat: Format.FrFrench));
Console.WriteLine("French Int: " + i);
Console.WriteLine("1,000,000".TryTo<int>(out i));
Console.WriteLine("English Int: " + i);
Console.WriteLine("500 000 000".TryTo<long>(out l, stringFormat: Format.FrFrench));
Console.WriteLine("French Long: " + l);
Console.WriteLine("500".TryTo<short>(out s));
Console.WriteLine("English Short: " + s);
bool b;
Console.WriteLine("true".TryTo<bool>(out b));
Console.WriteLine("True Parsed: " + b);
Console.WriteLine("false".TryTo<bool>(out b));
Console.WriteLine("False Parsed: " + b);
DateTime d;
Console.WriteLine("20140101010101".TryTo<DateTime>(out d, "yyyyMMddHHmmss"));
Console.WriteLine("yyyyMMddHHmmss Parsed: " + d);
Console.WriteLine("20140101 010101".TryTo<DateTime>(out d, "yyyyMMdd HHmmss"));
Console.WriteLine("yyyyMMdd HHmmss Parsed: " + d);
Console.WriteLine("2014-01-01 01:01:01".TryTo<DateTime>(out d));
Console.WriteLine("yyyy-MM-dd HH:mm:ss Parsed: " + d);
Console.WriteLine("01:01:01 2014/01/01".TryTo<DateTime>(out d, "HH:mm:ss yyyy/MM/dd"));
Console.WriteLine("HH:mm:ss yyyy/MM/dd Parsed: " + d);
Test t;
Console.WriteLine("Test1".TryTo<Test>(out t));
Console.WriteLine("Test.Test1 Parsed: " + t);
Console.WriteLine("Test.Test1 Parsed equal? " + (t == Test.Test1));
Console.ReadLine();
}
}
}
A Last Word
Thus, is an extension library for string.TryTo
conversion based on a very simple, yet, productive idea. Hope, this would help you refactor your code easily and/or save some of your dev time. Do let me know your thoughts about it ;). Do not feel shy to add Conversion methods related to user defined IConvertible struct
. All you need to do is add a new entry in dictionary and create conversion function of delegate ConvertFunc<T>
type (and that's it!!!).
Note: In case you need to add or remove any culture, make sure the corresponding CultureInfo
object is at the same index in the array as the int
value of the Format Enum
.
History
- 16th November, 2014: V1 of the proposed solution