2010-01-21 16 views
8

Sto lavorando a un controllo che può assumere un numero di tipi di dati diversi (tutto ciò che implementa IComparable).Conversione di tipo generale senza rischi Eccezioni

ho bisogno di essere in grado di confrontare questi con un'altra variabile passata.

Se il tipo di dati principale è un DateTime, e io sono passata una stringa, ho bisogno di

  • tentativo di convertire il Stringa a un DateTime per eseguire un confronto Data.
  • se la stringa non può essere convertita in un DateTime, quindi eseguire un confronto tra stringhe.

Quindi ho bisogno di un modo generale per tentare di convertire da qualsiasi tipo a qualsiasi tipo. Abbastanza facile, .Net ci fornisce la classe TypeConverter.

Ora, il meglio che posso fare per determinare se la stringa può essere convertita in un DateTime è utilizzare le eccezioni. Se ConvertFrom genera un'eccezione, so che non posso fare la conversione e devo fare il confronto tra stringhe.

Quello che segue è il migliore che ho ottenuto:

 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 dei nostri standard a condizione del lavoro che:

eccezioni dovrebbero essere sollevate solo quando una situazione Eccezione - vale a dire. si è verificato un errore.

Ma questa non è una situazione eccezionale. Ho bisogno di un altro modo per aggirarlo.

La maggior parte dei tipi di variabile ha un metodo TryParse che restituisce un valore booleano per consentire di determinare se la conversione è riuscita o meno. Ma non esiste alcun metodo TryConvert disponibile per TypeConverter. CanConvertFrom solo dermine se è possibile convertire tra questi tipi e non considera i dati effettivi da convertire. Anche il metodo IsValid è inutile.

Qualche idea?

EDIT

non posso usare AS e IS. Non conosco i due tipi di dati al momento della compilazione. Quindi non so cosa sia come ed è !!!

EDIT

Ok inchiodato il bastardo. Non è ordinato come Marc Gravells, ma funziona (spero). Grazie per l'ispirazione Marc. Lavorerò sul riordino quando avrò il tempo, ma ho un po 'di bugfix con cui devo andare avanti.

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

Non proprio, c'è la questione sa che cosa il tipo sta per essere convertito. Qui non lo faccio. La risposta a questa domanda non mi aiuta affatto. –

+0

Bene. Giusto. – jason

+0

Phew .. Pensavo che avrei chiuso la mia domanda. Ho passato ore su questo .. ;-) –

risposta

10

I farmaci generici sono un'opzione? Ecco un hack sfacciato che caccia il metodo TryParse e lo chiama tramite un (cache) Delegato:

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ì, wow che sembra solo il biglietto! Grazie. Dovrà solo aspettare fino a lunedì per provarlo al lavoro. Mi piace! –

+0

Bummer negato .. La soluzione sembrava buona, ma i dati vengono passati solo come oggetti. Non conosco i tipi al momento della compilazione. Mi rendo conto di non aver formulato la mia domanda molto bene, da qui la confusione. Questo è vicino però ... molto molto vicino, posso ancora provare a usare la parte di riflessione di cui sono sicuro. –

+0

Woohoo! L'ho distorto un po 'per trovare la soluzione. Vedi la modifica nel mio post. Grazie molto! –

0

Così ho bisogno di un modo generale per tentare di convertire da qualsiasi tipo a qualsiasi tipo. Abbastanza facile, .Net ci fornisce la classe TypeConverter.

Stai chiedendo troppo.

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

dovrei essere in grado di convertire un Cat ad un Dog?

Troverete che il vostro problema è molto più facile da risolvere se specificate più precisamente (preferibilmente esattamente) ciò che volete che sia il comportamento del metodo. Quindi, annota gli input attesi e quello che vuoi che l'output sia in ogni caso possibile. Quindi il tuo metodo dovrebbe scrivere da solo.

Così adesso abbiamo questa specifica:

Se il tipo di dati principale è un DateTime, e sto approvato una String, ho bisogno di

tentativo di convertire il String ad un DateTime per eseguire un confronto Date. se il String non può essere convertito in un DateTime quindi fare un confronto 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

Voglio un metodo bool TypeConverter.TryConvert (oggetto inObject, out object ConvertedObjected). Un po 'come DateTime.TryParse. Voglio fare esattamente ciò che fa l'esempio di codice che ho postato, ma senza che venga sollevata l'eccezione. Non sto davvero facendo oggetti qui, solo tipi di variabili di base: int, stringhe, DateTimes .. Ho pensato che la mia domanda fosse abbastanza chiara. –

+0

@Pongus: non è chiaro. Che tipo stai cercando di convertire inObject? Stai cercando di convertire nel tipo di 'ConvertedObjected' (sic)? – jason

+0

mi scuso. È un controllo di rete. Ho bisogno di ordinare e filtrare su qualsiasi tipo di dati che mi viene lanciato fintanto che è IComparable. –

4

direi che questo codice in realtà dovrebbe generare eccezioni quando non può capire una conversione. Se i due argomenti passati sono DateTime.Now e Color.Fuschsia, non è possibile fare alcun confronto significativo tra di essi, quindi qualsiasi valore restituito sarebbe errato. Questa è la definizione del momento giusto per lanciare un'eccezione.

Se è assolutamente necessario evitare le eccezioni, non è possibile eseguire ciò che si desidera con i tipi arbitrari. Ogni tipo ha le sue regole su quali valori può analizzare e il convertitore non ha modo di dirlo in anticipo. (Vale a dire, come hai notato, sa che a volte è possibile convertire unoin uno DateTime, ma non è progettato per sapere che "1/1/2010" è un valore valido DateTime mentre "Fred" non è .)

+0

In questa circostanza è perfettamente valido che gli input siano DateTime e Colore. Ovviamente, come i loro tipi di dati non elaborati, non possono essere confrontati. Questo è il motivo per cui voglio verificare se possono essere convertiti in modo da poter effettuare un confronto utile. Voglio solo controllare, non voglio che questa sia una condizione di errore. –

+0

Quindi, quello che vuoi è solo un metodo per verificare se una conversione avrà successo, senza effettivamente effettuare la conversione? Questo non è possibile con tipi arbitrari. 'TypeConverter' è progettato per tentare la conversione e generare eccezioni in caso di errore. Puoi controllare i tipi comuni che sai come gestire, come 'DateTime' e' Double' e così via, e chiama i loro metodi 'TryParse'. Ma funziona solo per i tipi che controlli. – Auraseer

5

Se non è possibile scrivere senza eccezioni, è possibile isolare il codice problematico dal refactoring in un metodo in questo 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 si dovrebbe essere di passaggio in tipi nullable come l'uscita parametro, in modo che null rappresenti un valore non definito (poiché non potrebbe eseguire una conversione) anziché il valore predefinito (ovvero 0 per int)

Problemi correlati