2010-01-21 13 views
8

Estoy trabajando en un control que puede tomar varios tipos de datos diferentes (cualquier cosa que implemente IComparable).Conversión de tipo general sin riesgo Excepciones

necesito para poder compararlos con otra variable que se pasa.

Si el tipo de datos principal es un DateTime, y estoy pasa una cadena, necesito

  • intento de convertir el Cadena a DateTime para realizar una comparación de fecha.
  • si la Cadena no se puede convertir a un DateTime y luego hacer una comparación de Cadena.

Así que necesito una forma general de intentar convertir de cualquier tipo a cualquier tipo. Lo suficientemente fácil, .Net nos proporciona la clase TypeConverter.

Ahora, lo mejor que puedo hacer para determinar si el String se puede convertir a un DateTime es usar excepciones. Si ConvertFrom genera una excepción, sé que no puedo hacer la conversión y tengo que hacer la comparación de cadenas.

El siguiente es lo mejor que tengo:

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

parte de nuestros estándares en estado de trabajo que:

excepciones sólo deben plantearse cuando una situación de excepción - es decir. un error es encontrado

Pero esta no es una situación excepcional. Necesito otra forma de evitarlo.

La mayoría de los tipos de variables tienen un método TryParse que devuelve un valor booleano que le permite determinar si la conversión se realizó correctamente o no. Pero no hay un método TryConvert disponible para TypeConverter. CanConvertFrom solo afecta si es posible convertir entre estos tipos y no considera los datos reales que se convertirán. El método IsValid también es inútil.

¿Alguna idea?

EDITAR

no puedo usar AS y ES. No conozco ninguno de los tipos de datos en tiempo de compilación. ¡¡Así que no sé qué decir "As" y "Es" !!!

EDITAR

Ok clavado al bastardo. No es tan limpio como Marc Gravells, pero funciona (espero). Gracias por la inspiración Marc. Trabajaré para ponerlo en orden cuando tenga tiempo, pero tengo un montón de correcciones de errores con las que tengo que lidiar.

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

realidad, no existe la cuestión sabe lo que va a ser convertido en el tipo. Aquí no. La respuesta a esa pregunta no me ayuda en absoluto. –

+0

Bien. Lo suficientemente justo. – jason

+0

Phew ... Pensé que iba a cerrar mi pregunta. He pasado horas en este ... ;-) –

Respuesta

10

¿Son genéricos una opción? Aquí hay un truco descarado que caza el método TryParse y lo llama por medio de un delegado (caché):

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

Sí, ¡guau que se ve solo el boleto! Gracias. Tendrán que esperar hasta el lunes para probarlo en el trabajo. ¡Gusta! –

+0

Bummer denegado .. La solución se veía bien, pero solo me están pasando los datos como objetos. No sé los tipos en tiempo de compilación. Me doy cuenta de que no he formulado mi pregunta muy bien, de ahí la confusión. Sin embargo, esto está muy cerca ... muy cerca, todavía puedo probar el uso de la parte de reflexión. Estoy seguro. –

+0

Woohoo! Lo han torcido un poco para llegar a la solución. Ver la edición en mi publicación. ¡Muchas gracias! –

0

por eso es necesario de manera general para intentar convertir de cualquier tipo a cualquier tipo. Fácilmente, .Net nos proporciona la clase TypeConverter.

Usted está pidiendo demasiado.

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

Debería ser capaz de convertir un Cat a un Dog?

Encontrará que su problema es mucho más fácil de resolver si especifica de manera más precisa (preferiblemente exactamente) cuál es el comportamiento del método. Por lo tanto, anote las entradas esperadas y lo que quiere que sea la salida en cada caso posible. Entonces tu método debería escribirse a sí mismo.

Así que ahora tenemos esta especificación:

Si el tipo de datos principal es un DateTime, y me pasó una String, necesito

intento de convertir el String a un DateTime para llevar a cabo una comparación de Date. si el String no se puede convertir a DateTime y luego hacer una comparación de 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(); 
} 
+0

Quiero un bool TypeConverter.TryConvert (objeto en Objeto, objeto obviado ConvertedObjected) método. Un poco como DateTime.TryParse. Quiero hacer exactamente lo que muestra el código que publiqué, pero sin que se produzca la excepción. Realmente no estoy haciendo objetos aquí, solo tipos de variables básicas: Ints, cadenas, DateTimes .. Pensé que mi pregunta era bastante clara. –

+0

@Pongus: No está claro. ¿A qué tipo estás intentando convertir 'inObject'? ¿Estás tratando de convertir al tipo de 'ConvertedObjected' (sic)? – jason

+0

mis disculpas. Es un control de cuadrícula Necesito ordenar y filtrar cualquier tipo de datos que se me arrojen, siempre que sea IComparable. –

4

Yo diría que este código realmente debe lanzar excepciones cuando no puede encontrar una conversión. Si los dos argumentos pasados ​​son DateTime.Now y Color.Fuschsia, no puede hacer una comparación significativa entre ellos, por lo que cualquier valor que devuelva sería incorrecto. Esa es la definición del momento adecuado para lanzar una excepción.

Si necesitas evitar las excepciones, no es posible hacer lo que quieras con tipos arbitrarios. Cada tipo tiene sus propias reglas sobre qué valores puede analizar, y el convertidor no tiene manera de saberlo con antelación. (Es decir, como habrás notado, sabe que a veces puedes convertir un string en un DateTime, pero no está diseñado para saber que "1/1/2010" es un DateTime válido mientras que "Fred" no está .)

+0

En esta circunstancia, es perfectamente válido para las entradas como DateTime y Color. Obviamente, como sus tipos de datos brutos no se pueden comparar. Es por eso que quiero verificar si se pueden convertir para que se pueda hacer una comparación útil. Solo quiero comprobar, no quiero que esto sea una condición de error. –

+0

¿Entonces lo que quiere es solo un método para verificar si una conversión tendría éxito, sin realmente hacer la conversión? Esto no es posible con tipos arbitrarios. 'TypeConverter' está diseñado para intentar la conversión y lanzar excepciones en caso de error. Puede verificar los tipos comunes que sabe cómo manejar, como 'DateTime' y' Double' y demás, y llamar a sus métodos 'TryParse'. Pero eso solo funciona para los tipos que controlas. – Auraseer

5

Si no es posible escribir sin excepciones, se puede aislar el código problemático por refactorización en un método de este modo:

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

Idealmente debería estar pasando en los tipos anulables como la salida parámetro, de modo que null representa un valor indefinido (porque no pudo hacer una conversión) en lugar del valor predeterminado (es decir, 0 para int)

Cuestiones relacionadas