一般类型转换没有exception的风险

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

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

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

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

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

现在,我可以做的最好的事情是确定String是否可以转换为DateTime是使用exception。 如果ConvertFrom引发exception,我知道我不能进行转换并且必须进行字符串比较。

以下是我得到的最好的:

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

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

只有在exception情况下才会引发exception – 即。 遇到错误。

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

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

有任何想法吗?

编辑

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

编辑

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

  public static class CleanConverter { ///  /// Stores the cache of all types that can be converted to all types. ///  private static Dictionary<Type, Dictionary> _Types = new Dictionary<Type, Dictionary> (); ///  /// Try parsing. ///  ///  ///  ///  public static bool TryParse ( IComparable s, ref IComparable value ) { // First get the cached conversion method. Dictionary type1Cache = null; ConversionCache type2Cache = null; if ( !_Types.ContainsKey ( s.GetType () ) ) { type1Cache = new Dictionary (); _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 ); } ///  /// Stores the method to convert from Type1 to Type2 ///  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 () } ); } } } 

仿制药是一种选择吗? 这是一个厚颜无耻的黑客攻击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(string s, out T value) { return Cache.TryParse(s, out value); } internal static class Cache { 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); } } } } 

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

 public static bool TryConvert(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表示未定义的值(因为它无法进行转换)而不是默认值(即0表示int)

我认为这个代码真的应该在它无法找到转换时抛出exception。 如果传入的两个参数是DateTime.NowColor.Fuschsia ,则它们之间不能进行有意义的比较,因此您返回的任何值都是错误的。 这是抛出exception的正确时间的定义。

如果你绝对需要避免exception,那么就不可能用任意类型做你想做的事情。 每种类型都有自己的规则,可以解析哪些值,转换器无法提前告知。 (也就是说,正如您所注意到的,它知道您有时可以将string转换为DateTime ,但它并不是设计为知道“1/1/2010”是有效的DateTime而“Fred”不是。)

所以我需要一种通用的方法来尝试从任何类型转换为任何类型。 很简单,.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(); }