一般类型转换没有冒外线的风险

Mon*_*ong 7 c# type-conversion

我正在研究一个控件,它可以采用许多不同的数据类型(任何实现IComparable的东西).

我需要能够将这些与传入的另一个变量进行比较.

如果主数据类型是DateTime,并且我传递了一个String,我需要

  • 尝试将String转换为DateTime以执行日期比较.
  • 如果String无法转换为DateTime,则执行String比较.

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

现在,我可以做的最好的事情是确定String是否可以转换为DateTime是使用异常.如果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" );
        }
Run Code Online (Sandbox Code Playgroud)

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

只有在异常情况下才会引发异常 - 即.遇到错误.

但这不是特例.我需要另一种方式来解决它.

大多数变量类型都有一个TryParse方法,该方法返回一个布尔值,以便您确定转换是否成功.但TypeConverter没有可用的TryConvert方法.如果可以在这些类型之间进行转换并且不考虑要转换的实际数据,则CanConvertFrom仅限于dermines.所述的IsValid方法也是无用的.

有任何想法吗?

编辑

我不能使用AS和IS.我不知道编译时的数据类型.所以我不知道该怎么做才是!

编辑

好的钉死了这个混蛋.它不像Marc Gravells那么整洁,但它有效(我希望).感谢Marc的鼓舞.当我有空的时候,我会努力整理它,但我有一堆错误修正,我必须继续.

    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 () } );
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 9

仿制药是一种选择吗?这是一个厚颜无耻的黑客攻击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);
            }            
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


ICR*_*ICR 5

如果无法在没有异常的情况下编写它,您可以通过将其重构为如下方法来隔离有问题的代码:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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