我正在研究一种可以采用多种不同数据类型的控件(任何实现IComparable的数据类型)。
我需要能够将这些与传入的另一个变量进行比较。
如果主数据类型是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" );
}
我们的部分工作标准表明:
只有在异常情况下才会引发异常 - 即。遇到错误。
但这并非特殊情况。我需要另一种方法。
大多数变量类型都有一个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 () } );
}
}
}
答案 0 :(得分: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);
}
}
}
}
答案 1 :(得分: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;
}
}
理想情况下,您应该将可空类型作为输出参数传递,以便null表示未定义的值(因为它无法进行转换)而不是默认值(即0表示int)
答案 2 :(得分:4)
我认为这段代码在无法找出转换时应该抛出异常。如果传入的两个参数是DateTime.Now
和Color.Fuschsia
,则它们之间无法进行有意义的比较,因此您返回的任何值都是错误的。这是抛出异常的正确时间的定义。
如果您绝对需要避免异常,则不可能使用任意类型执行您想要的操作。每种类型都有自己的规则,可以解析哪些值,转换器无法提前告知。 (也就是说,正如您已经注意到的那样,它知道您有时可以将string
转换为DateTime
,但它并不是设计为知道“1/1/2010”是有效DateTime
而“Fred”不是。)
答案 3 :(得分:0)
所以我需要一种通用的方法来尝试从任何类型转换为任何类型。很简单,.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();
}