Prześlij obiekt do T

91

Analizuję plik XML z XmlReaderklasą w .NET i pomyślałem, że mądrze byłoby napisać ogólną funkcję analizy, aby generalnie odczytywać różne atrybuty. Wymyśliłem następującą funkcję:

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

Jak sobie uświadomiłem, nie działa to do końca tak, jak planowałem; zgłasza błąd w przypadku typów pierwotnych, takich jak intlub double, ponieważ rzutowanie nie może zostać przekonwertowane z typu a stringna typ liczbowy. Czy istnieje sposób, aby moja funkcja przeważała w zmodyfikowanej formie?

Kasper Holdum
źródło

Odpowiedzi:

207

Najpierw sprawdź, czy można go rzucić.

if (readData is T) {
    return (T)readData;
} 
try {
   return (T)Convert.ChangeType(readData, typeof(T));
} 
catch (InvalidCastException) {
   return default(T);
}
Pion
źródło
1
Zmieniłem wiersz za pomocą Convert.ChangeType na: 'return (T) Convert.ChangeType (readData, typeof (T), System.Globalization.CultureInfo.InstalledUICulture.NumberFormat), aby działał w różnych konfiguracjach kulturowych.
Kasper Holdum
2
To jest poprawna odpowiedź. Ale mógłbym argumentować, że metoda try / catch jest tutaj całkowicie zbędna. Szczególnie biorąc pod uwagę wyciszony wyjątek. Myślę, że część if (readData to T) {...} jest wystarczającą próbą.
pim
Przed konwersją możesz sprawdzić, czy readDate ma wartość null. Jeśli tak, zwróć default (T).
Manuel Koch
Otrzymuję komunikat „Obiekt musi implementować IConvertible”.
Casey Crookston,
19

Czy próbowałeś Convert.ChangeType ?

Jeśli metoda zawsze zwraca ciąg, który wydaje mi się dziwny, ale to nie ma znaczenia, być może ten zmieniony kod zrobiłby to, co chcesz:

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
źródło
Początkowo przyjrzałem się Convert.ChangeType, ale zdecydowałem, że nie jest to przydatne w tej operacji z jakiegoś dziwnego powodu. Ty i Bob udzieliliście tej samej odpowiedzi, więc zdecydowałem się na połączenie Twoich odpowiedzi, więc unikałem używania instrukcji try, ale nadal użyłem „return (T) readData”, gdy było to możliwe.
Kasper Holdum
11

próbować

if (readData is T)
    return (T)(object)readData;
Sadegh
źródło
3

Możesz wymagać, aby typ był typem referencyjnym:

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

A następnie wykonaj inną, która używa typów wartości i 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
źródło
3

Właściwie problemem tutaj jest użycie ReadContentAsObject. Niestety ta metoda nie spełnia jej oczekiwań; chociaż powinien wykryć najbardziej odpowiedni typ wartości, w rzeczywistości zwraca ciąg, bez względu na wszystko (można to zweryfikować za pomocą Reflectora).

Jednak w twoim konkretnym przypadku znasz już typ, na który chcesz rzutować, dlatego powiedziałbym, że używasz złej metody.

Zamiast tego spróbuj użyć ReadContentAs, jest to dokładnie to, czego potrzebujesz.

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAs(typeof(T), null);
    return (T)readData;
}
baretta
źródło
Wygląda dość kompaktowo i elegancko. Jednak rozwiązanie w zaakceptowanej odpowiedzi wykorzystuje ChangeType, który jest zgodny z wieloma różnymi kulturami, ponieważ akceptuje IFormatProvider. Ponieważ jest to konieczne dla projektu, pozostanę przy tym rozwiązaniu.
Kasper Holdum,
2

Możesz przypuszczalnie przekazać, jako parametr, delegata, który skonwertuje z ciągu znaków na T.

ChrisW
źródło
1

Dodaj ograniczenie `` klasy '' (lub bardziej szczegółowe, takie jak klasa bazowa lub interfejs oczekiwanych obiektów T):

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

lub where T : IMyInterfacelub where T : new()itp

Ricardo Villamil
źródło
1

Właściwie odpowiedzi prowadzą do interesującego pytania, co chcesz, aby Twoja funkcja robiła w przypadku błędu.

Może bardziej sensowne byłoby skonstruowanie go w formie metody TryParse, która próbuje wczytać do T, ale zwraca false, jeśli nie można tego zrobić?

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

edycja: teraz, kiedy o tym myślę, czy naprawdę muszę wykonać test convert.changetype? czy linia as nie próbuje już tego zrobić? Nie jestem pewien, czy zrobienie tego dodatkowego wywołania typu changetype faktycznie coś da. W rzeczywistości może to po prostu zwiększyć obciążenie przetwarzania, generując wyjątek. Jeśli ktoś wie o różnicy, która sprawia, że ​​warto to zrobić, napisz!

genki
źródło