【问题标题】:General type conversion without risking Exceptions通用类型转换而不冒异常风险
【发布时间】:2011-01-07 20:25:36
【问题描述】:

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

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

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

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

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

现在,确定字符串是否可以转换为 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" );
        }

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

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

但这不是一个例外情况。我需要另一种解决方法。

大多数变量类型都有一个TryParse 方法,该方法返回一个布尔值以允许您确定转换是否成功。但是 TypeConverter 没有可用的 TryConvert 方法。 CanConvertFrom 仅在可以在这些类型之间进行转换的情况下进行定义,并且不考虑要转换的实际数据。 IsValid 方法也没用。

有什么想法吗?

编辑

我不能使用 AS 和 IS。我在编译时不知道这两种数据类型。所以我不知道要做什么和要做什么!!!

编辑

好的,把这个混蛋钉死了。它不像 Marc Gravells 那样整洁,但它确实有效(我希望如此)。感谢马克的灵感。当我有时间时会努力整理它,但我有一些我必须继续进行的错误修复。

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

【问题讨论】:

  • 不是真的,问题知道类型将被转换成什么。在这里我没有。这个问题的答案对我一点帮助都没有。
  • 呸..我以为我要结束我的问题了。我花了几个小时在这个.. ;-)
  • 当涉及到这样的标准时,您需要意识到您也在处理其他人的代码。其他人可能没有使用相同的标准。因此,虽然标准规定您不应在代码中为控制流引发异常,但如果由第 3 方编写的代码不允许您在不捕获异常的情况下轻松或优雅地编写它,那么您将不得不捕获一个例外。当然可能有一个不使用异常的解决方案,如果没有,我只想指出这一点。
  • 在这种情况下使用DateTime.TryParse 有什么问题? DateTime 只是一个具体的例子吗?
  • @sapph 是的,它可以是任何类型。

标签: c# type-conversion


【解决方案1】:

泛型是一种选择吗?这是一个厚颜无耻的 hack,它寻找 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);
            }            
        }
    }
}

【讨论】:

  • 是的,哇,看起来就是门票!谢谢。只需要等到星期一才能在工作中尝试一下。喜欢!
  • Bummer denied.. 解决方案看起来不错,但我只是将数据作为对象传递。我不知道编译时的类型。我意识到我没有很好地提出我的问题,因此造成了混乱。虽然这很接近.. 非常非常接近,我仍然可以尝试使用它的反射部分我确定。
  • 哇哦!稍微扭曲一下以提出解决方案。请参阅我的帖子中的编辑。非常感谢!
  • @MarcGravell 我编辑了它以使string.TryParse() 不会失败,你能检查(/批准编辑)它吗?有没有更好的方法来做(T)(object)"string"?显然 Windows Phone 7 没有实现 TypeDescriptor,这对它来说是个好办法吗?
【解决方案2】:

如果无法无例外地编写它,您可以通过将其重构为如下方法来隔离有问题的代码:

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

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

【讨论】:

    【解决方案3】:

    我认为这段代码在无法确定转换时确实应该抛出异常。如果传入的两个参数是DateTime.NowColor.Fuschsia,则无法对它们进行有意义的比较,因此返回的任何值都是错误的。这就是引发异常的正确时间的定义。

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

    【讨论】:

    • 在这种情况下,输入为 DateTime 和 Color 是完全有效的。显然,作为它们的原始数据类型,它们无法进行比较。这就是为什么我想检查它们是否可以转换,以便进行有用的比较。我只是想检查一下,我不希望这是一个错误情况。
    • 那么您想要的只是一种检查转换是否成功的方法,而无需实际进行转换?这对于任意类型是不可能的。 TypeConverter 旨在尝试转换并在失败时抛出异常。您可以检查您知道如何处理的常见类型,例如DateTimeDouble 等,并调用它们的TryParse 方法。但这仅适用于您检查的类型。
    【解决方案4】:

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

    你要求的太多了。

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

    我应该能够将Cat 转换为Dog 吗?

    如果您更准确地(最好是准确地)指定您希望方法的行为是什么,您会发现您的问题更容易解决。因此,在每种可能的情况下,写下预期的输入以及您希望输出的内容。然后你的方法应该自己写。

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

    如果主要数据类型是DateTime,并且传递给我的是String,我需要

    尝试将String 转换为DateTime 以执行Date 比较。 如果String 无法转换为DateTime,则进行String 比较。

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

    【讨论】:

    • 我想要一个 bool TypeConverter.TryConvert(object inObject, out object ConvertedObjected) 方法。有点像 DateTime.TryParse。我想做我发布的代码示例所做的事情,但不会引发异常。我在这里并没有真正做对象,只是基本的变量类型:整数、字符串、日期时间。我认为我的问题很清楚。
    • @Pongus:还不清楚。您想将inObject 转换为什么类型?您是否尝试转换为ConvertedObjected(原文如此)的类型?
    • 我很抱歉。它是一个网格控件。只要它是 IComparable,我就需要对向我抛出的任何数据类型进行排序和过滤。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-28
    • 2011-08-17
    • 2017-07-08
    • 1970-01-01
    • 2015-10-02
    • 2021-02-02
    • 2016-01-16
    相关资源
    最近更新 更多