Привести объект к T

91

Я разбираю XML-файл с XmlReaderклассом в .NET, и я подумал, что было бы разумно написать общую функцию синтаксического анализа для общего чтения различных атрибутов. Я придумал такую ​​функцию:

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

Как я понял, это работает не совсем так, как я планировал; он выдает ошибку с примитивными типами, такими как intили double, поскольку приведение не может преобразовать из a stringв числовой тип. Есть ли способ, чтобы моя функция превалировала в измененной форме?

Каспер Холдум
источник

Ответы:

208

Сначала проверьте, можно ли его использовать.

if (readData is T) {
    return (T)readData;
} 
try {
   return (T)Convert.ChangeType(readData, typeof(T));
} 
catch (InvalidCastException) {
   return default(T);
}
Боб
источник
1
Я изменил строку с Convert.ChangeType на: 'return (T) Convert.ChangeType (readData, typeof (T), System.Globalization.CultureInfo.InstalledUICulture.NumberFormat), чтобы заставить его работать с различными культурными конфигурациями.
Kasper Holdum 07
2
Это правильный ответ. Но я мог бы возразить, что здесь попытка / улов полностью излишни. Особенно учитывая приглушенное исключение. Я думаю, что часть if (readData is T) {...} является достаточной попыткой.
ПИМ
Вы можете проверить, является ли readDate нулевым перед его преобразованием. Если да, верните значение по умолчанию (T).
Мануэль Кох
Я получаю: «Объект должен реализовать IConvertible».
Кейси Крукстон,
19

Вы пробовали Convert.ChangeType ?

Если метод всегда возвращает строку, которую я считаю странной, но помимо этого, возможно, этот измененный код будет делать то, что вы хотите:

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAsObject();
    return (T)Convert.ChangeType(readData, typeof(T));
}
Лассе В. Карлсен
источник
Сначала я взглянул на Convert.ChangeType, но решил, что он бесполезен для этой операции по какой-то странной причине. Вы и Боб оба дали один и тот же ответ, и я решил смешать ваши ответы, поэтому я избегал использования операторов try, но по-прежнему использовал return (T) readData, когда это возможно.
Kasper Holdum
11

пытаться

if (readData is T)
    return (T)(object)readData;
Садех
источник
3

Вы можете потребовать, чтобы тип был ссылочным:

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

А затем сделайте еще один, который использует типы значений и 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

Собственно, проблема здесь в использовании ReadContentAsObject. К сожалению, этот метод не оправдывает ожиданий; хотя он должен определять наиболее подходящий тип значения, он фактически возвращает строку, независимо от того, что (это можно проверить с помощью Reflector).

Однако в вашем конкретном случае вы уже знаете тип, к которому хотите применить, поэтому я бы сказал, что вы используете неправильный метод.

Попробуйте вместо этого использовать ReadContentAs, это именно то, что вам нужно.

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAs(typeof(T), null);
    return (T)readData;
}
Baretta
источник
Смотрится довольно компактно и элегантно. Однако решение в принятом ответе использует ChangeType, который совместим с несколькими разными культурами, поскольку он принимает IFormatProvider. Поскольку это необходимо для проекта, я буду придерживаться этого решения.
Kasper Holdum 07
2

Предположительно вы можете передать в качестве параметра делегата, который будет преобразовывать из строки в T.

ChrisW
источник
1

Добавьте ограничение класса (или более подробно, например, базовый класс или интерфейс ваших ожидаемых T-объектов):

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

или where T : IMyInterfaceили where T : new()и т. д.

Рикардо Вильямил
источник
1

Собственно, ответы поднимают интересный вопрос: что вы хотите, чтобы ваша функция выполняла в случае ошибки.

Может быть, было бы разумнее построить его в виде метода TryParse, который пытается читать в T, но возвращает false, если это невозможно?

    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: теперь, когда я думаю об этом, действительно ли мне нужно проводить тест convert.changetype? Разве строка as уже не пытается это сделать? Я не уверен, что этот дополнительный вызов changetype на самом деле что-то дает. Фактически, это может просто увеличить накладные расходы на обработку, создав исключение. Если кто-то знает разницу, которая стоит того, напишите, пожалуйста!

Genki
источник