2013-08-09 14 views
7

La mia domanda riguarda il controllo dei tipi in una catena di metodi generici. Diciamo che ho un metodo di estensione che tenta di convertire una matrice di byte in int, decimale, stringa o DateTime.Evitare un eccessivo controllo dei tipi nei metodi generici?

public static T Read<T>(this ByteContainer ba, int size, string format) where T : struct, IConvertible 
{ 
    var s = string.Concat(ba.Bytes.Select(b => b.ToString(format)).ToArray());    
    var magic = FromString<T>(s); 
    return (T)Convert.ChangeType(magic, typeof(T)); 
} 

Questo chiama un metodo chiamato FromString che traduce la stringa concatenata in un tipo specifico. Purtroppo, la logica di business è completamente dipendente dal tipo T. Quindi io alla fine con un blocco if-else megalitica:

private static T FromString<T>(string s) where T : struct 
{ 
    if (typeof(T).Equals(typeof(decimal))) 
    { 
     var x = (decimal)System.Convert.ToInt32(s)/100; 
     return (T)Convert.ChangeType(x, typeof(T)); 
    } 
    if (typeof(T).Equals(typeof(int))) 
    { 
     var x = System.Convert.ToInt32(s); 
     return (T)Convert.ChangeType(x, typeof(T)); 
    } 
    if (typeof(T).Equals(typeof(DateTime))) 
     ... etc ... 
} 

A questo punto, preferirei più metodi con lo stesso nome e diversi tipi di ritorno, qualcosa sulla falsariga di questo:

// <WishfulThinking> 
private static decimal FromString<T>(string s) 
{ 
    return (decimal)System.Convert.ToInt32(s)/100; 
}  
private static int FromString<T>(string s) 
{ 
    return System.Convert.ToInt32(s); 
} 
// </WishfulThinking> 

... ma mi rendo conto che questo non è valido, come T non possono essere costretti a un tipo specifico, e senza di essa, tutti i metodi avranno lo stesso in conflitto firma.

Esiste un modo fattibile per implementare FromString senza un controllo eccessivo dei caratteri? O potrebbe esserci un modo migliore per affrontare del tutto questo problema?

+0

Questo mi sembra un problema, è possibile risolvi con le espressioni. – lukegravitt

risposta

10

Oppure potrebbe esserci un modo migliore per affrontare questo problema del tutto?

Certo, ce n'è uno: si può fare ciascun convertitore in un lambda, fare un dizionario di loro, e li usa per la conversione, in questo modo:

private static IDictionary<Type,Func<string,object>> Converters = new Dictionary<Type,Func<string,object>> { 
    {typeof(int), s => Convert.ChangeType(System.Convert.ToInt32(s), typeof(int))} 
, {typeof(decimal), s => Convert.ChangeType((decimal)System.Convert.ToInt32(s)/100, typeof(decimal))} 
, ... // And so on 
}; 
public static T Read<T>(this ByteContainer ba, int size, string format) where T : struct, IConvertible { 
    Func<string,object> converter; 
    if (!Converters.TryGetValue(typeof(T), out converter)) { 
     throw new ArgumentException("Unsupported type: "+typeof(T)); 
    } 
    var s = string.Concat(ba.Bytes.Select(b => b.ToString(format)).ToArray()); 
    return (T)converter(s); 
} 
+2

Penso che questo approccio mi abbia solo fatto impazzire. Avrò bisogno di passare un po 'di tempo con questo ... – MadHenchbot

+0

Invece di usare un dizionario statico, che ne dici di usare una classe statica generica privata 'MyConverters ' con un campo di tipo 'Func ' che per impostazione predefinita è un metodo che controllerà se 'T' è un tipo noto e getterà un'eccezione o aggiornerà il campo come appropriato? Quindi 'Leggi ' potrebbe richiamare 'MyConverters .Converter' senza alcuna logica condizionale necessaria dopo il primo utilizzo. – supercat

+0

@dasblinkenlight Grazie per avermi esposto a questa idea!La tua soluzione ha risolto il mio problema e mi ha fatto pensare a C# in un modo nuovo ed eccitante. Molto apprezzato. – MadHenchbot

6

In generale, se si deve scrivere una logica che deve sempre controllare il tipo di un parametro di tipo generico, non si sta effettivamente scrivendo codice che benefici dall'essere generico. Stando così le cose, e supponendo che il vero problema che stai cercando di risolvere sia la necessità di convertire un array di byte in un tipo predefinito prevedibile che rappresenta, ti consiglio di abbandonare questo approccio e utilizzare i metodi nello BitConverter class.

Nel punto in cui è possibile determinare il valore di T, è sufficiente chiamare il metodo appropriato sulla classe BitConverter.

Aggiornamento: Se una soluzione generica è necessario, io suggerirei qualcosa di simile a s' dasblinkenlight risposta, anche se avrei lasciato il chiamante iniettare il convertitore (dopo tutto, il chiamante conosce il tipo di risultato requisito) , che evita il problema di mantenere un elenco di funzioni di conversione accanto al metodo generico:

public static T Read<T>(this ByteContainer ba, int size, string format, 
         Func<string, T> converter) where T : struct, IConvertible 
{ 
    var s = string.Concat(ba.Bytes.Select(b => b.ToString(format)).ToArray()); 
    return converter(s); 
} 
+0

Sono assolutamente d'accordo con te. Sfortunatamente, ci sono così tanti processi di traduzione associati alla conversione che non sono sicuro che BitConverter sia sufficiente nel mio caso. – MadHenchbot