Lançar o objeto para T

90

Estou analisando um arquivo XML com a XmlReaderclasse em .NET e pensei que seria inteligente escrever uma função de análise genérica para ler diferentes atributos genericamente. Eu criei a seguinte função:

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

Como pude perceber, isso não funciona inteiramente como planejei; ele lança um erro com tipos primitivos como intou double, uma vez que uma conversão não pode ser convertida de um stringpara um tipo numérico. Existe alguma maneira de minha função prevalecer na forma modificada?

Kasper Holdum
fonte

Respostas:

204

Primeiro verifique se ele pode ser lançado.

if (readData is T) {
    return (T)readData;
} 
try {
   return (T)Convert.ChangeType(readData, typeof(T));
} 
catch (InvalidCastException) {
   return default(T);
}
Prumo
fonte
1
Mudei a linha com Convert.ChangeType para: 'return (T) Convert.ChangeType (readData, typeof (T), System.Globalization.CultureInfo.InstalledUICulture.NumberFormat) para fazê-lo funcionar em várias configurações culturais diferentes.
Kasper Holdum
2
Essa é a resposta correta. Mas eu poderia argumentar que try / catch é totalmente redundante aqui. Especialmente considerando a exceção silenciosa. Acho que a parte if (readData is T) {...} é uma tentativa suficiente.
pimbrouwers
Você pode verificar se readDate é nulo antes de convertê-lo. Se sim, retorne o padrão (T).
Manuel Koch
Recebo "O objeto deve implementar IConvertible."
Casey Crookston
19

Você já experimentou o Convert.ChangeType ?

Se o método sempre retorna uma string, o que eu acho estranho, mas isso está além do ponto, então talvez esse código alterado faria o que você deseja:

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
fonte
Inicialmente, dei uma olhada em Convert.ChangeType, mas decidi que não era útil para essa operação por algum motivo estranho. Você e Bob forneceram a mesma resposta, e eu decidi misturar suas respostas, então evitei usar declarações try, mas ainda usei 'return (T) readData' quando possível.
Kasper Holdum
10

experimentar

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

Você pode exigir que o tipo seja um tipo de referência:

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

E então faça outro que use tipos de valor 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
     //...
 }
Tom Ritter
fonte
3

Na verdade, o problema aqui é o uso de ReadContentAsObject. Infelizmente, esse método não corresponde às suas expectativas; embora deva detectar o tipo mais apropriado para o valor, ele na verdade retorna uma string, não importa o que aconteça (isso pode ser verificado usando o Refletor).

No entanto, no seu caso específico, você já sabe o tipo para o qual deseja fazer a projeção, portanto, eu diria que está usando o método errado.

Tente usar ReadContentAs, é exatamente o que você precisa.

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAs(typeof(T), null);
    return (T)readData;
}
Baretta
fonte
Parece bastante compacto e elegante. No entanto, a solução na resposta aceita usa ChangeType, que é compatível com várias culturas diferentes, pois aceita um IFormatProvider. Como essa é uma necessidade do projeto, irei manter essa solução.
Kasper Holdum
2

Você pode provavelmente passar, como um parâmetro, um delegado que converterá de string em T.

ChrisW
fonte
1

Adicione uma restrição de 'classe' (ou mais detalhada, como uma classe base ou interface de seus 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;
}

ou where T : IMyInterfaceou where T : new(), etc

Ricardo Villamil
fonte
1

Na verdade, as respostas trazem uma questão interessante, que é o que você quer que sua função faça em caso de erro.

Talvez faria mais sentido construí-lo na forma de um método TryParse que tenta ler em T, mas retorna falso se não puder ser feito?

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

edit: agora que pensei sobre isso, eu realmente preciso fazer o teste convert.changetype? a linha as já não tenta fazer isso? Não tenho certeza se fazer essa chamada de changetype adicional realmente realiza alguma coisa. Na verdade, ele pode apenas aumentar a sobrecarga de processamento, gerando exceção. Se alguém souber de alguma diferença que valha a pena fazer, poste!

Genki
fonte