Lancer l'objet en T


91

J'analyse un fichier XML avec la XmlReaderclasse en .NET et j'ai pensé qu'il serait judicieux d'écrire une fonction d'analyse générique pour lire différents attributs de manière générique. J'ai proposé la fonction suivante:

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

Lorsque je me suis rendu compte, cela ne fonctionne pas entièrement comme je l'avais prévu; il renvoie une erreur avec des types primitifs tels que intou double, car une conversion ne peut pas passer d'un type a stringà un type numérique. Y a-t-il un moyen pour ma fonction de prévaloir sous une forme modifiée?

Réponses:


208

Vérifiez d'abord s'il peut être lancé.

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

1
J'ai changé la ligne avec Convert.ChangeType en: 'return (T) Convert.ChangeType (readData, typeof (T), System.Globalization.CultureInfo.InstalledUICulture.NumberFormat) pour le faire fonctionner sur diverses configurations culturelles différentes.
Kasper Holdum

2
C'est la bonne réponse. Mais je pourrais faire valoir que le try / catch est totalement redondant ici. Surtout compte tenu de l'exception en sourdine. Je pense que la partie if (readData est T) {...} est une tentative suffisante.
pim

Vous pouvez vérifier si readDate est nul avant de le convertir. Si c'est le cas, renvoyez la valeur par défaut (T).
Manuel Koch

J'obtiens "L'objet doit implémenter IConvertible."
Casey Crookston

19

Avez-vous essayé Convert.ChangeType ?

Si la méthode retourne toujours une chaîne, que je trouve étrange, mais que ce n'est pas le problème, alors peut-être que ce code modifié ferait ce que vous voulez:

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

J'ai d'abord jeté un coup d'œil à Convert.ChangeType mais j'ai décidé que ce n'était pas utile pour cette opération pour une raison étrange. Vous et Bob avez tous les deux fourni la même réponse, et j'ai décidé d'aller avec un mélange entre vos réponses, donc j'ai évité d'utiliser les instructions try, mais j'ai quand même utilisé 'return (T) readData' lorsque c'était possible.
Kasper Holdum

11

essayer

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

3

Vous pouvez exiger que le type soit un type de référence:

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

Et puis faites-en un autre qui utilise des types de valeur et 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

En fait, le problème ici est l'utilisation de ReadContentAsObject. Malheureusement, cette méthode ne répond pas à ses attentes; alors qu'il doit détecter le type le plus approprié pour la valeur, il renvoie en fait une chaîne, quoi qu'il arrive (cela peut être vérifié à l'aide de Reflector).

Cependant, dans votre cas spécifique, vous connaissez déjà le type vers lequel vous souhaitez effectuer un cast, je dirais donc que vous utilisez la mauvaise méthode.

Essayez plutôt d'utiliser ReadContentAs, c'est exactement ce dont vous avez besoin.

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

Semble assez compact et élégant. Cependant, la solution dans la réponse acceptée utilise ChangeType qui est compatible avec plusieurs cultures différentes car il accepte un IFormatProvider. Puisque c'est une nécessité pour le projet, je m'en tiendrai à cette solution.
Kasper Holdum

2

Vous pouvez vraisemblablement transmettre, en tant que paramètre, un délégué qui convertira de chaîne en T.


1

Ajoutez une contrainte de `` classe '' (ou plus détaillée, comme une classe de base ou une interface de vos objets T attendus):

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

En fait, les réponses soulèvent une question intéressante, c'est ce que vous voulez que votre fonction fasse en cas d'erreur.

Peut-être qu'il serait plus logique de le construire sous la forme d'une méthode TryParse qui tente de lire dans T, mais retourne false si cela ne peut pas être fait?

    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: maintenant que j'y pense, dois-je vraiment faire le test convert.changetype? la ligne as essaie-t-elle déjà de le faire? Je ne suis pas sûr que faire cet appel de changement de type supplémentaire accomplisse réellement quelque chose. En fait, cela pourrait simplement augmenter la surcharge de traitement en générant une exception. Si quelqu'un connaît une différence qui en vaut la peine, veuillez poster!

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.