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?

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

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

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;

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
     //...
 }

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

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.


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


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!

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.