代码之家  ›  专栏  ›  技术社区  ›  Mongus Pong

不冒异常风险的常规类型转换

  •  8
  • Mongus Pong  · 技术社区  · 15 年前

    我正在开发一个可以接受许多不同数据类型(实现IComparable的任何类型)的控件。

    我需要能够将它们与传入的另一个变量进行比较。

    如果主数据类型是日期时间,并且我被传递了一个字符串,那么我需要

    • 尝试将字符串转换为日期时间以执行日期比较。
    • 如果字符串无法转换为日期时间,则执行字符串比较。

    所以我需要一种通用的方法来尝试从任何类型转换为任何类型。很简单,.net为我们提供了 TypeConverter 班级。

    现在,为了确定字符串是否可以转换为日期时间,我能做的最好的工作就是使用异常。如果convertFrom引发异常,我知道无法进行转换,必须进行字符串比较。

    以下是我所得到的最好的:

            string theString = "99/12/2009";
            DateTime theDate = new DateTime ( 2009, 11, 1 );
    
            IComparable obj1 = theString as IComparable;
            IComparable obj2 = theDate as IComparable;
    
            try
            {
                TypeConverter converter = TypeDescriptor.GetConverter ( obj2.GetType () );
                if ( converter.CanConvertFrom ( obj1.GetType () ) )
                {
                    Console.WriteLine ( obj2.CompareTo ( converter.ConvertFrom ( obj1 ) ) );
                    Console.WriteLine ( "Date comparison" );
                }
            }
            catch ( FormatException )
            {
                Console.WriteLine ( obj1.ToString ().CompareTo ( obj2.ToString () ) );
                Console.WriteLine ( "String comparison" );
            }
    

    我们工作标准的一部分规定:

    只有在出现异常情况(即遇到错误)时才应引发异常。

    但这并不是一个特殊的情况。我需要另一种方法。

    大多数变量类型都有 TryParse 方法,该方法返回一个布尔值,以允许您确定转换是否成功。但没有Tryconvert方法可用于类型转换器。 CanConvertFrom 只有当真皮可以在这些类型之间转换,并且不考虑要转换的实际数据时。这个 IsValid 方法也是无用的。

    有什么想法吗?

    编辑

    我不能按原样使用。我在编译时不知道这两种数据类型。所以我不知道该做什么和该做什么!!!!

    编辑

    好吧,把那个混蛋钉死了。它不像马克·格雷弗斯那么整洁,但它很管用(我希望如此)。谢谢你的住院马克。当我有时间的时候,我会把它整理好,但是我有一堆错误需要处理。

        public static class CleanConverter
        {
            /// <summary>
            /// Stores the cache of all types that can be converted to all types.
            /// </summary>
            private static Dictionary<Type, Dictionary<Type, ConversionCache>> _Types = new Dictionary<Type, Dictionary<Type, ConversionCache>> ();
    
            /// <summary>
            /// Try parsing.
            /// </summary>
            /// <param name="s"></param>
            /// <param name="value"></param>
            /// <returns></returns>
            public static bool TryParse ( IComparable s, ref IComparable value )
            {
                // First get the cached conversion method.
                Dictionary<Type, ConversionCache> type1Cache = null;
                ConversionCache type2Cache = null;
    
                if ( !_Types.ContainsKey ( s.GetType () ) )
                {
                    type1Cache = new Dictionary<Type, ConversionCache> ();
    
                    _Types.Add ( s.GetType (), type1Cache );
                }
                else
                {
                    type1Cache = _Types[s.GetType ()];
                }
    
                if ( !type1Cache.ContainsKey ( value.GetType () ) )
                {
                    // We havent converted this type before, so create a new conversion
                    type2Cache = new ConversionCache ( s.GetType (), value.GetType () );
    
                    // Add to the cache
                    type1Cache.Add ( value.GetType (), type2Cache );
                }
                else
                {
                    type2Cache = type1Cache[value.GetType ()];
                }
    
                // Attempt the parse
                return type2Cache.TryParse ( s, ref value );
            }
    
            /// <summary>
            /// Stores the method to convert from Type1 to Type2
            /// </summary>
            internal class ConversionCache
            {
                internal bool TryParse ( IComparable s, ref IComparable value )
                {
                    if ( this._Method != null )
                    {
                        // Invoke the cached TryParse method.
                        object[] parameters = new object[] { s, value };
                        bool result = (bool)this._Method.Invoke ( null,  parameters);
    
                        if ( result )
                            value = parameters[1] as IComparable;
    
                        return result;
                    }
                    else
                        return false;
    
                }
    
                private MethodInfo _Method;
                internal ConversionCache ( Type type1, Type type2 )
                {
                    // Use reflection to get the TryParse method from it.
                    this._Method = type2.GetMethod ( "TryParse", new Type[] { type1, type2.MakeByRefType () } );
                }
            }
        }
    
    4 回复  |  直到 13 年前
        1
  •  10
  •   Chuck Savage    13 年前

    仿制药是一种选择吗?这是一个厚颜无耻的黑客 TryParse 方法并通过(缓存的)委托调用它:

    using System;
    using System.Reflection;
    
    static class Program
    {
        static void Main()
        {
            int i; float f; decimal d;
            if (Test.TryParse("123", out i)) {
                Console.WriteLine(i);
            }
            if (Test.TryParse("123.45", out f)) {
                Console.WriteLine(f);
            }
            if (Test.TryParse("123.4567", out d)) {
                Console.WriteLine(d);
            }
        }
    }
    public static class Test
    {
        public static bool TryParse<T>(string s, out T value) {
            return Cache<T>.TryParse(s, out value);
        }
        internal static class Cache<T> {
            public static bool TryParse(string s, out T value)
            {
                return func(s, out value);
            }    
            delegate bool TryPattern(string s, out T value);
            private static readonly TryPattern func;
            static Cache()
            {
                MethodInfo method = typeof(T).GetMethod(
                    "TryParse", new Type[] { typeof(string), typeof(T).MakeByRefType() });
                if (method == null) {
                    if (typeof(T) == typeof(string))
                        func = delegate(string x, out T y) { y = (T)(object)x; return true; };
                    else
                        func = delegate(string x, out T y) { y = default(T); return false; };
                } else {
                    func = (TryPattern) Delegate.CreateDelegate(typeof(TryPattern),method);
                }            
            }
        }
    }
    
        2
  •  5
  •   ICR    15 年前

    如果不可能不加例外地编写它,您可以通过将有问题的代码重构为这样的方法来隔离它:

    public static bool TryConvert<T, U>(T t, out U u)
    {
        try
        {
            TypeConverter converter = TypeDescriptor.GetConverter(typeof(U));
            if (!converter.CanConvertFrom(typeof(T)))
            {
                u = default(U);
                return false;
            }
            u = (U)converter.ConvertFrom(t);
            return true;
        }
        catch (Exception e)
        {
            if (e.InnerException is FormatException)
            {
                u = default(U);
                return false;
            }
    
            throw;
        }
    }
    

    理想情况下,您应该将可为空的类型作为输出参数传递,这样,空值表示一个未定义的值(因为它无法进行转换),而不是默认值(即0表示in t)。

        3
  •  4
  •   Auraseer    15 年前

    我会争辩说,当代码无法找到转换时,它真的应该抛出异常。如果传入的两个参数是 DateTime.Now Color.Fuschsia ,您不能对它们进行有意义的比较,因此返回的任何值都是错误的。这就是抛出异常的正确时间的定义。

    如果您绝对需要避免异常,就不可能用任意类型来做您想要做的事情。每种类型都有自己的规则来决定它可以解析哪些值,而转换器也没有办法提前告诉我们这一点。(也就是说,正如您所注意到的,它知道您有时可以转换 string DateTime 但是,它并不是为了知道“1/1/2010”是有效的 日期时间 而“弗雷德”不是。)

        4
  •  0
  •   jason    15 年前

    所以我需要一种通用的方法来尝试从任何类型转换为任何类型。很简单,.net为我们提供了 TypeConverter 班级。

    你要求太多了。

    class Animal { }
    class Dog : Animal { }
    class Cat : Animal { }
    

    我能转换一个 Cat 到A Dog ?

    如果您更精确地(最好精确地)指定您希望方法的行为是什么,您会发现解决问题要容易得多。所以,写下期望的输入,以及在每个可能的情况下,您希望输出是什么。然后您的方法应该自己编写。

    所以现在我们有了这个规范:

    如果主数据类型是 DateTime 我通过了 String 我需要

    尝试转换 日期时间 执行 Date 比较。 如果 无法转换为 日期时间 那就做一个 字符串 比较。

    int CompareTo(DateTime d, object o) {
        string s = o as string;
        if(s != null) {
            DateTime dt;
            if(dt.TryParse(s, out dt)) {
                return d.CompareTo(dt);
            }
            else {
                return d.ToString().CompareTo(s);
            }
        }
        throw new InvalidOperationException();
    }