Общее преобразование типов без риска исключения - PullRequest
7 голосов
/ 21 января 2010

Я работаю над элементом управления, который может принимать несколько различных типов данных (все, что реализует IComparable).

Мне нужно сравнить их с другой переданной переменной.

Если основным типом данных является DateTime, и мне передана строка, мне нужно

  • попытка преобразовать строку в DateTime для сравнения дат.
  • если строка не может быть преобразована в DateTime, тогда выполните сравнение строки.

Так что мне нужен общий способ попытаться преобразовать любой тип в любой тип. Достаточно просто, .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 , который возвращает логическое значение, позволяющее определить, успешно ли выполнено преобразование. Но нет метода TryConvert, доступного для TypeConverter. CanConvertFrom работает только в том случае, если возможно преобразование между этими типами, и не учитывает фактические данные для преобразования. Метод IsValid также бесполезен.

Есть идеи?

EDIT

Я не могу использовать AS и IS. Я не знаю ни одного типа данных во время компиляции. Так что я не знаю, что и как и * !!! 1044 *

EDIT

Хорошо, прибил ублюдка. Это не так аккуратно, как Марк Гравеллс, но это работает (я надеюсь). Спасибо за вдохновение, Марк. Я поработаю над тем, чтобы привести его в порядок, когда у меня будет время, но у меня есть небольшой стек исправлений, с которыми мне придется поработать.

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

Ответы [ 4 ]

9 голосов
/ 22 января 2010

Являются ли дженерики вариант? Вот нахальный взлом, который охотится на метод 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);
            }            
        }
    }
}
5 голосов
/ 21 января 2010

Если невозможно написать его без исключений, вы можете изолировать проблемный код путем рефакторинга его в метод, подобный следующему:

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)

4 голосов
/ 21 января 2010

Я бы сказал, что этот код действительно должен генерировать исключения, когда он не может определить преобразование. Если переданы два аргумента DateTime.Now и Color.Fuschsia, вы не можете провести никакого значимого сравнения между ними, поэтому любое возвращаемое вами значение будет неверным. Это определение правильного времени для исключения.

Если вам абсолютно необходимо избегать исключений, невозможно делать то, что вы хотите, с произвольными типами. У каждого типа есть свои собственные правила о том, какие значения он может анализировать, и преобразователь не может сказать об этом заранее. (То есть, как вы заметили, он знает, что иногда вы можете конвертировать string в DateTime, но он не предназначен для того, чтобы знать, что «01.01.2010» является действительным DateTime пока "Фред" нет.)

0 голосов
/ 21 января 2010

Так что мне нужен общий способ попытаться преобразовать любой тип в любой тип. Достаточно просто, .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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...