Click here to Skip to main content
13,597,485 members
Click here to Skip to main content
Add your own
alternative version

Stats

10.5K views
138 downloads
28 bookmarked
Posted 16 Nov 2014
Licenced CPOL

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

, 16 Nov 2014
Rate this:
Please Sign up or sign in to vote.
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 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 works 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, takes the pain to create 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 function 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 a 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 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 this function requires 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... lets see the code...

The Code Itself

Without much talks, 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);
        }
    }
}
</int>

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 abt 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 thats it!!!).

NOTE: In case you need to add 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

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)

Share

About the Author

D Sarthi Maheshwari
Architect
France France
An open-minded, passionate, adaptive and resourceful software solution provider who is architecting/developing solutions. With 10+ yrs of experience in technology and in various work environments, ranging from pure research to trading rooms & from start-up to world renowned companies; always eager to deliver better, faster and reliable solutions.

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 5 Pin
Jason Down4-Dec-14 10:14
memberJason Down4-Dec-14 10:14 
GeneralMy vote of 5 Pin
Marcel Kalinowski18-Nov-14 20:36
memberMarcel Kalinowski18-Nov-14 20:36 
QuestionMy Vote of 5 Pin
vighnu16-Nov-14 22:19
membervighnu16-Nov-14 22:19 
GeneralMy vote of 5 Pin
Praneet Nadkar16-Nov-14 17:05
memberPraneet 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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04-2016 | 2.8.180621.3 | Last Updated 16 Nov 2014
Article Copyright 2014 by D Sarthi Maheshwari
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid