65.9K
CodeProject 正在变化。 阅读更多。
Home

String.TryTo<T> 和 String.To<Nullable> 扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (15投票s)

2014年11月16日

CPOL

3分钟阅读

viewsIcon

20502

downloadIcon

178

String 类上的扩展方法,用于将字符串转换为 IConvertible 结构体和可空类型。

引言

在编写 C# 程序时,有时需要将 string 值解析为 intboollong 等或等效的可空类型。在我短暂的 C# 程序员生涯中,我注意到代码中反复使用 Int.TryParsebool.TryParse 等函数。这并非有害,但会降低生产力,并且随着项目成熟(又何况是国际化/本地化呢?)需要大量的重构。最重要的是,我看到开发者询问如何解析为 Nullable 类型。

另一个事实是,当多名程序员在项目上工作时,他们会根据自己的方便,使用不同的解析方法(例如,使用 Convert/struct 方法(Parse TryParse)/IConvertible 方法),这会影响代码的可读性。一些管理良好的项目会花费精力创建一个单独的项目并在需要时引用它,这是一个好习惯。

此解决方案背后的理念是提供一种将所有 string 转换函数分组为扩展类的方式,该类可以轻松地以最小的努力支持本地化需求。

关于扩展

提议的扩展类只公开两个公共扩展方法

  • TryTo<T> (T: struct, IConvertible): 执行到非可空 struct 类型的转换。我已经包含了对 Enum 解析的支持。
  • ToNullable<T> (T: struct, IConvertible): 执行到可空 struct 类型的转换。我已经包含了对 Enum 解析的支持。

事实上,这个扩展的设计并非火箭科学,所以我直接复制代码签名如下

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

对于敏锐的读者来说,这些方法签名最大的区别在于 ToNullable<T> 方法中缺少 out T result 参数。我省略了这个 out 参数,因为它不够方便,因为定义是:“如果某事物无法转换为类型 T,则 Nullable<T> 的值必须为NULL否则,则为转换后的值。”(希望读者会同意。)

这些方法签名中另一个令人困惑的参数是 string dtForm = "yyyy-MM-dd HH:mm:ss"。为什么这个函数需要 dateTime 格式字符串?我所要做的就是 TryTo<int>。实际上,可以通过为每种类型设计重载函数(读者可以自行选择)来做到这一点。不过,我将其保持原样并提供了一个默认值,这样用户就不必为每次转换而担心了。当然,当你进行 TryTo<DateTime> 时,不要忘记提供一个好的值。

最后一个参数是 ENUM,它映射到 culture-info。当然,我可以直接要求 System.Globalization.CultureInfo 实例。但我更倾向于在程序启动时,由程序从某个文件/数据库/配置/UI(程序员喜欢的方式)中将 culture info 值读取为 string,然后程序调用 cultureString.TryTo<Format>() 将其转换为 culture enum 值,然后使用此值进行其他转换。(一些优秀的程序员会发现,在我的代码中,使用字典/数组进行如此简单的转换操作耗时过多,但他们非常清楚如何根据自己的需求调整此程序。)

好了,各位,我说了太多废话……让我们看看代码……

代码本身

不多说,以下是代码

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

以下代码片段展示了转换示例

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

最后的话

因此,这是一个基于非常简单但富有成效的思想的 string.TryTo 转换扩展库。希望这有助于您轻松重构代码和/或节省开发时间。请告诉我您对此的看法;)。不要害羞,添加与用户定义的 IConvertible struct 相关的转换方法。您所需要做的就是向字典中添加一个新条目,并创建一个 delegate ConvertFunc<T> 类型的转换函数(仅此而已!!!)。

注意:如果您需要添加或删除任何区域性,请确保相应的 CultureInfo 对象与 Format Enumint 值在数组中的索引相同。

历史

  • 2014年11月16日:提议解决方案 V1
© . All rights reserved.