Click here to Skip to main content
15,890,438 members
Articles / General Programming / String
Tip/Trick

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

Rate me:
Please Sign up or sign in to vote.
4.81/5 (15 votes)
16 Nov 2014CPOL3 min read 19.6K   148   28   4
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:

C#
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
C#
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:

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
France France
An open-minded, passionate, adaptive and resourceful software solution developer. He daydreams of code and spend nights coding his thoughts.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Jason Down4-Dec-14 10:14
Jason Down4-Dec-14 10:14 
GeneralMy vote of 5 Pin
Marcel Kalinowski18-Nov-14 20:36
Marcel Kalinowski18-Nov-14 20:36 
Thanks for doing all the TryParse coding for us! Smile | :)
QuestionMy Vote of 5 Pin
vighnu16-Nov-14 22:19
vighnu16-Nov-14 22:19 
GeneralMy vote of 5 Pin
Praneet Nadkar16-Nov-14 17:05
Praneet Nadkar16-Nov-14 17:05 

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.