Lanzar objeto a T

91

Estoy analizando un archivo XML con la XmlReaderclase en .NET y pensé que sería inteligente escribir una función de análisis genérico para leer diferentes atributos de forma genérica. Se me ocurrió la siguiente función:

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

Como me di cuenta, esto no funciona del todo como lo había planeado; arroja un error con tipos primitivos como into double, ya que una conversión no puede convertir de un stringtipo a numérico. ¿Existe alguna forma de que mi función prevalezca en forma modificada?

Kasper Holdum
fuente

Respuestas:

207

Primero verifique si se puede lanzar.

if (readData is T) {
    return (T)readData;
} 
try {
   return (T)Convert.ChangeType(readData, typeof(T));
} 
catch (InvalidCastException) {
   return default(T);
}
Beto
fuente
1
Cambié la línea con Convert.ChangeType a: 'return (T) Convert.ChangeType (readData, typeof (T), System.Globalization.CultureInfo.InstalledUICulture.NumberFormat) para que funcione en varias configuraciones culturales diferentes.
Kasper Holdum
2
Esta es la respuesta correcta. Pero podría argumentar que el try / catch es totalmente redundante aquí. Especialmente considerando la excepción silenciada. Creo que la parte if (readData is T) {...} es un intento suficiente.
pim
Puede comprobar si readDate es nulo antes de convertirlo. Si es así, devuelva el valor predeterminado (T).
Manuel Koch
Aparece "El objeto debe implementar IConvertible".
Casey Crookston
19

¿Ha probado Convert.ChangeType ?

Si el método siempre devuelve una cadena, lo que me parece extraño, pero eso es más allá del punto, entonces tal vez este código cambiado haría lo que quieres:

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAsObject();
    return (T)Convert.ChangeType(readData, typeof(T));
}
Lasse V. Karlsen
fuente
Inicialmente eché un vistazo a Convert.ChangeType pero decidí que no era útil para esta operación por alguna extraña razón. Tú y Bob proporcionaron la misma respuesta, y decidí ir con una combinación entre sus respuestas, así que evité usar declaraciones de prueba, pero aún usé 'return (T) readData' cuando fue posible.
Kasper Holdum
11

tratar

if (readData is T)
    return (T)(object)readData;
Sadegh
fuente
3

Podría requerir que el tipo sea un tipo de referencia:

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

Y luego haz otro que use tipos de valor y 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
     //...
 }
Tom Ritter
fuente
3

En realidad, el problema aquí es el uso de ReadContentAsObject. Desafortunadamente, este método no está a la altura de sus expectativas; si bien debería detectar el tipo más apropiado para el valor, en realidad devuelve una cadena, pase lo que pase (esto se puede verificar usando Reflector).

Sin embargo, en su caso específico, ya sabe el tipo al que desea transmitir, por lo tanto, diría que está utilizando el método incorrecto.

Intente usar ReadContentAs en su lugar, es exactamente lo que necesita.

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAs(typeof(T), null);
    return (T)readData;
}
baretta
fuente
Parece bastante compacto y elegante. Sin embargo, la solución en la respuesta aceptada utiliza ChangeType, que es compatible con múltiples culturas diferentes, ya que acepta un IFormatProvider. Dado que esta es una necesidad para el proyecto, seguiré con esa solución.
Kasper Holdum
2

Presumiblemente, puede pasar, como parámetro, un delegado que se convertirá de cadena a T.

ChrisW
fuente
1

Agregue una restricción de 'clase' (o más detallada, como una clase base o interfaz de sus objetos T esperados):

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(), etc.

Ricardo Villamil
fuente
1

En realidad, las respuestas plantean una pregunta interesante, que es qué desea que haga su función en caso de error.

¿Quizás tendría más sentido construirlo en forma de un método TryParse que intenta leer en T, pero devuelve falso si no se puede hacer?

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

editar: ahora que lo pienso, ¿realmente necesito hacer la prueba convert.changetype? ¿la línea as ya no intenta hacer eso? No estoy seguro de que hacer esa llamada de tipo de cambio adicional realmente logre algo. En realidad, podría aumentar la sobrecarga de procesamiento generando una excepción. Si alguien sabe de una diferencia que hace que valga la pena hacerla, ¡publíquela!

genki
fuente