Lancia l'oggetto su T


91

Sto analizzando un file XML con la XmlReaderclasse in .NET e ho pensato che sarebbe stato intelligente scrivere una funzione di analisi generica per leggere diversi attributi in modo generico. Ho ideato la seguente funzione:

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAsObject();
    return (T)readData;
}

Come ho capito, questo non funziona del tutto come avevo pianificato; genera un errore con tipi primitivi come into double, poiché un cast non può convertire da a stringin un tipo numerico. Esiste un modo per far prevalere la mia funzione in forma modificata?

Risposte:


207

Prima controlla per vedere se può essere lanciato.

if (readData is T) {
    return (T)readData;
} 
try {
   return (T)Convert.ChangeType(readData, typeof(T));
} 
catch (InvalidCastException) {
   return default(T);
}

1
Ho cambiato la riga con Convert.ChangeType in: 'return (T) Convert.ChangeType (readData, typeof (T), System.Globalization.CultureInfo.InstalledUICulture.NumberFormat) per farlo funzionare su varie configurazioni culturali differenti.
Kasper Holdum

2
Questa è la risposta corretta. Ma potrei sostenere che il try / catch è totalmente ridondante qui. Soprattutto considerando l'eccezione in sordina. Penso che la parte if (readData is T) {...} sia un tentativo sufficiente.
pim

È possibile verificare se readDate è nullo prima di convertirlo. In tal caso, restituire il valore predefinito (T).
Manuel Koch,

Ottengo "L'oggetto deve implementare IConvertible".
Casey Crookston,

19

Hai provato Convert.ChangeType ?

Se il metodo restituisce sempre una stringa, che trovo strano, ma questo è oltre il punto, allora forse questo codice modificato farebbe quello che vuoi:

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAsObject();
    return (T)Convert.ChangeType(readData, typeof(T));
}

Inizialmente ho dato un'occhiata a Convert.ChangeType ma ho deciso che non era utile per questa operazione per qualche strano motivo. Sia tu che Bob avete fornito la stessa risposta, e ho deciso di scegliere un mix tra le vostre risposte, quindi ho evitato di usare le istruzioni try ma ho comunque usato 'return (T) readData' quando possibile.
Kasper Holdum

11

provare

if (readData is T)
    return (T)(object)readData;

3

Potresti richiedere che il tipo sia un tipo di riferimento:

 private static T ReadData<T>(XmlReader reader, string value) where T : class
 {
     reader.MoveToAttribute(value);
     object readData = reader.ReadContentAsObject();
     return (T)readData;
 }

E poi fai un altro che usa i tipi di valore e TryParse ...

 private static T ReadDataV<T>(XmlReader reader, string value) where T : struct
 {
     reader.MoveToAttribute(value);
     object readData = reader.ReadContentAsObject();
     int outInt;
     if(int.TryParse(readData, out outInt))
        return outInt
     //...
 }

3

In realtà, il problema qui è l'uso di ReadContentAsObject. Sfortunatamente, questo metodo non è all'altezza delle sue aspettative; mentre dovrebbe rilevare il tipo più appropriato per il valore, in realtà restituisce una stringa, qualunque cosa accada (questo può essere verificato usando Reflector).

Tuttavia, nel tuo caso specifico, conosci già il tipo a cui vuoi eseguire il cast, quindi direi che stai usando il metodo sbagliato.

Prova a usare ReadContentAs invece, è esattamente ciò di cui hai bisogno.

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAs(typeof(T), null);
    return (T)readData;
}

Sembra abbastanza compatto ed elegante. Tuttavia, la soluzione nella risposta accettata utilizza ChangeType che è compatibile con più impostazioni cultura diverse poiché accetta un IFormatProvider. Poiché questa è una necessità per il progetto, mi atterrò a quella soluzione.
Kasper Holdum

2

È presumibilmente possibile passare, come parametro, un delegato che convertirà da stringa a T.


1

Aggiungi un vincolo di "classe" (o più dettagliato, come una classe base o un'interfaccia dei tuoi oggetti T previsti):

private static T ReadData<T>(XmlReader reader, string value) where T : class
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAsObject();
    return (T)readData;
}

o where T : IMyInterfaceo where T : new(), ecc


1

In realtà, le risposte sollevano una domanda interessante, che è ciò che vuoi che la tua funzione faccia in caso di errore.

Forse avrebbe più senso costruirlo sotto forma di un metodo TryParse che tenta di leggere in T, ma restituisce false se non può essere fatto?

    private static bool ReadData<T>(XmlReader reader, string value, out T data)
    {
        bool result = false;
        try
        {
            reader.MoveToAttribute(value);
            object readData = reader.ReadContentAsObject();
            data = readData as T;
            if (data == null)
            {
                // see if we can convert to the requested type
                data = (T)Convert.ChangeType(readData, typeof(T));
            }
            result = (data != null);
        }
        catch (InvalidCastException) { }
        catch (Exception ex)
        {
            // add in any other exception handling here, invalid xml or whatnot
        }
        // make sure data is set to a default value
        data = (result) ? data : default(T);
        return result;
    }

modifica: ora che ci penso, ho davvero bisogno di fare il test convert.changetype? la linea as non prova già a farlo? Non sono sicuro che questa chiamata aggiuntiva al changetype realizzi effettivamente qualcosa. In realtà, potrebbe semplicemente aumentare il sovraccarico di elaborazione generando un'eccezione. Se qualcuno sa di una differenza che vale la pena fare, per favore pubblica!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.