Wirf das Objekt auf T.

90

Ich analysiere eine XML-Datei mit der XmlReaderKlasse in .NET und dachte, es wäre klug, eine generische Analysefunktion zu schreiben, um verschiedene Attribute generisch zu lesen. Ich habe mir folgende Funktion ausgedacht:

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

Wie mir klar wurde, funktioniert dies nicht ganz so, wie ich es geplant habe. Bei primitiven Typen wie intoder doublewird ein Fehler ausgegeben , da eine Umwandlung nicht von einem stringin einen numerischen Typ konvertiert werden kann . Gibt es eine Möglichkeit für meine Funktion, sich in modifizierter Form durchzusetzen?

Kasper Holdum
quelle

Antworten:

204

Überprüfen Sie zuerst, ob es gegossen werden kann.

if (readData is T) {
    return (T)readData;
} 
try {
   return (T)Convert.ChangeType(readData, typeof(T));
} 
catch (InvalidCastException) {
   return default(T);
}
Bob
quelle
1
Ich habe die Zeile mit Convert.ChangeType geändert in: 'return (T) Convert.ChangeType (readData, typeof (T), System.Globalization.CultureInfo.InstalledUICulture.NumberFormat), damit es auf verschiedenen kulturellen Konfigurationen funktioniert.
Kasper Holdum
2
Dies ist die richtige Antwort. Aber ich könnte argumentieren, dass der Versuch / Fang hier völlig überflüssig ist. Besonders angesichts der gedämpften Ausnahme. Ich denke, der if (readData is T) {...} Teil ist ein ausreichender Versuch.
Pimbrouwers
Sie können überprüfen, ob readDate null ist, bevor Sie es konvertieren. Wenn ja, geben Sie den Standardwert (T) zurück.
Manuel Koch
Ich erhalte "Objekt muss IConvertible implementieren."
Casey Crookston
19

Haben Sie Convert.ChangeType ausprobiert ?

Wenn die Methode immer eine Zeichenfolge zurückgibt, die ich merkwürdig finde, aber das ist nicht der Punkt, dann würde dieser geänderte Code möglicherweise das tun, was Sie wollen:

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
quelle
Ich habe mir zunächst Convert.ChangeType angesehen, aber festgestellt, dass es aus irgendeinem Grund für diesen Vorgang nicht nützlich ist. Sie und Bob gaben beide die gleiche Antwort, und ich entschied mich für eine Mischung zwischen Ihren Antworten, sodass ich keine try-Anweisungen verwendete, aber nach Möglichkeit immer noch 'return (T) readData' verwendete.
Kasper Holdum
10

Versuchen

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

Möglicherweise muss der Typ ein Referenztyp sein:

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

Und dann machen Sie eine andere, die Werttypen und TryParse verwendet ...

 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
quelle
3

Tatsächlich ist das Problem hier die Verwendung von ReadContentAsObject. Leider entspricht diese Methode nicht ihren Erwartungen. Während es den am besten geeigneten Typ für den Wert erkennen sollte, gibt es tatsächlich eine Zeichenfolge zurück, egal was passiert (dies kann mit Reflector überprüft werden).

In Ihrem speziellen Fall kennen Sie jedoch bereits den Typ, in den Sie umwandeln möchten, daher würde ich sagen, dass Sie die falsche Methode verwenden.

Versuchen Sie stattdessen, ReadContentAs zu verwenden. Es ist genau das, was Sie brauchen.

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAs(typeof(T), null);
    return (T)readData;
}
Baretta
quelle
Sieht recht kompakt und elegant aus. Die Lösung in der akzeptierten Antwort verwendet jedoch ChangeType, das mit mehreren verschiedenen Kulturen kompatibel ist, da es einen IFormatProvider akzeptiert. Da dies eine Notwendigkeit für das Projekt ist, werde ich mich an diese Lösung halten.
Kasper Holdum
2

Vermutlich können Sie als Parameter einen Delegaten übergeben, der von Zeichenfolge in T konvertiert.

ChrisW
quelle
1

Fügen Sie eine 'Klassen'-Einschränkung hinzu (oder detaillierter, wie eine Basisklasse oder eine Schnittstelle Ihrer erwarteten T-Objekte):

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

oder where T : IMyInterfaceoder where T : new()usw.

Ricardo Villamil
quelle
1

Tatsächlich werfen die Antworten eine interessante Frage auf, die Ihre Funktion im Fehlerfall erfüllen soll.

Vielleicht wäre es sinnvoller, es in Form einer TryParse-Methode zu konstruieren, die versucht, in T einzulesen, aber false zurückgibt, wenn dies nicht möglich ist?

    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: muss ich jetzt, wo ich darüber nachdenke, wirklich den convert.changetype-Test durchführen? versucht die as-Zeile das nicht schon? Ich bin mir nicht sicher, ob dieser zusätzliche Änderungsaufruf tatsächlich etwas bewirkt. Tatsächlich kann dies nur den Verarbeitungsaufwand erhöhen, indem eine Ausnahme generiert wird. Wenn jemand einen Unterschied kennt, der es wert macht, geschrieben zu werden!

Genki
quelle