Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
192 views
in Technique[技术] by (71.8m points)

c# - General type conversion without risking Exceptions

I am working on a control that can take a number of different datatypes (anything that implements IComparable).

I need to be able to compare these with another variable passed in.

If the main datatype is a DateTime, and I am passed a String, I need to

  • attempt to convert the String to a DateTime to perform a Date comparison.
  • if the String cannot be converted to a DateTime then do a String comparison.

So I need a general way to attempt to convert from any type to any type. Easy enough, .Net provides us with the TypeConverter class.

Now, the best I can work out to do to determine if the String can be converted to a DateTime is to use exceptions. If the ConvertFrom raises an exception, I know I cant do the conversion and have to do the string comparison.

The following is the best I got :

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

Part of our standards at work state that :

Exceptions should only be raised when an Exception situation - ie. an error is encountered.

But this is not an exceptional situation. I need another way around it.

Most variable types have a TryParse method which returns a boolean to allow you to determine if the conversion has succeeded or not. But there is no TryConvert method available to TypeConverter. CanConvertFrom only dermines if it is possible to convert between these types and doesnt consider the actual data to be converted. The IsValid method is also useless.

Any ideas?

EDIT

I cannot use AS and IS. I do not know either data types at compile time. So I dont know what to As and Is to!!!

EDIT

Ok nailed the bastard. Its not as tidy as Marc Gravells, but it works (I hope). Thanks for the inpiration Marc. Will work on tidying it up when I get the time, but I've got a bit stack of bugfixes that I have to get on with.

    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 () } );
            }
        }
    }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Are generics an option? Here's a cheeky hack that hunts the TryParse method and calls it via a (cached) delegate:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...