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?

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

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

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


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

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

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.


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


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!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.