Conversione di tipo generale senza rischi Eccezioni

Sto lavorando a un controllo che può assumere un numero di tipi di dati diversi (tutto ciò che implementa IComparable).

Devo essere in grado di confrontarli con un’altra variabile passata.

Se il tipo di dati principale è un DateTime e ho passato una stringa, è necessario

  • tenta di convertire la stringa in un DateTime per eseguire un confronto di date.
  • se la stringa non può essere convertita in un DateTime, 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 class TypeConverter .

Ora, il meglio che posso fare per determinare se la stringa può essere convertita in un DateTime è usare 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 meglio che ho ricevuto:

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 di lavoro afferma che:

Le eccezioni dovrebbero essere sollevate solo quando si verifica una situazione di eccezione, ad es. si è verificato un errore.

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

La maggior parte dei tipi di variabili 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 è ansible convertire tra questi tipi e non considera i dati effettivi da convertire. Anche il metodo IsValid è inutile.

Qualche idea?

MODIFICARE

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

MODIFICARE

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 { ///  /// Stores the cache of all types that can be converted to all types. ///  private static Dictionary<Type, Dictionary> _Types = new Dictionary<Type, Dictionary> (); ///  /// Try parsing. ///  ///  ///  ///  public static bool TryParse ( IComparable s, ref IComparable value ) { // First get the cached conversion method. Dictionary type1Cache = null; ConversionCache type2Cache = null; if ( !_Types.ContainsKey ( s.GetType () ) ) { type1Cache = new Dictionary (); _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 ); } ///  /// Stores the method to convert from Type1 to Type2 ///  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 () } ); } } } 

I generici sono un’opzione? Ecco un hack sfacciato che caccia il metodo TryParse e lo chiama tramite un delegato (memorizzato nella cache):

 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(string s, out T value) { return Cache.TryParse(s, out value); } internal static class Cache { 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); } } } } 

Se non è ansible scriverlo senza eccezioni, puoi isolare il codice problematico rifasandolo in un metodo come questo:

 public static bool TryConvert(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 dovresti passare i tipi nullable come parametro di output, in modo che null rappresenti un valore non definito (perché non potrebbe eseguire una conversione) piuttosto che il valore predefinito (ovvero 0 per int)

Direi che questo codice dovrebbe davvero generare eccezioni quando non riesce a capire una conversione. Se i due argomenti passati sono DateTime.Now e Color.Fuschsia , non è ansible fare un confronto significativo tra loro, quindi qualsiasi valore restituito sarebbe errato. Questa è la definizione del momento giusto per lanciare un’eccezione.

Se devi assolutamente evitare le eccezioni, non è ansible fare ciò che vuoi 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 puoi convertire una string in un DateTime , ma non è progettato per sapere che “1/1/2010” è un DateTime valido mentre “Fred” non è .)

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

Stai chiedendo troppo.

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

Devo essere in grado di convertire un Cat in un Dog ?

Scoprirai che il tuo problema è molto più facile da risolvere se specifichi più precisamente (preferibilmente esattamente) ciò che desideri sia il comportamento del metodo. Quindi, annota gli input attesi e quello che vuoi che l’output sia in ogni caso ansible. Quindi il tuo metodo dovrebbe scrivere da solo.

Quindi adesso abbiamo questa specifica:

Se il tipo di dati principale è un DateTime e ho passato una String , è necessario

tenta di convertire la String in un DateTime per eseguire un confronto di Date . se la String non può essere convertita in un DateTime eseguire un confronto tra 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(); }