Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

String.TryTo<T> and String.To<Nullable> Extensions

0.00/5 (No votes)
16 Nov 2014 1  
Extension method on String class to convert string to IConvertible structs and nullable types

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)}
            };
        }

        //dateTimeFormat is ONLY needed when conversion to DateTime is called, else it would be ignored.
        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);
            }
        }

        //dateTimeFormat is ONLY needed when conversion to DateTime is called, 
        //else it would be ignored.
        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;
            
            // INT CONVERSION
            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);

            // LONG/SHORT CONVERSION
            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;
            //BOOL CONVERSION
            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;
            //DATETIME CONVERSION
            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;
            //ENUM CONVERSION
            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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here