Передати об'єкт Т


90

Я аналізую 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на числовий тип. Чи є спосіб для моєї функції переважати у зміненій формі?

Відповіді:


207

Спочатку перевірте, чи можна його кинути.

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), щоб він працював у різних різних культурних конфігураціях.
Каспер Холдум

2
Це правильна відповідь. Але я міг би аргументувати, що try / catch тут абсолютно зайвий. Особливо враховуючи приглушений виняток. Я думаю, що частина if (readData - T) {...} є достатньою спробою.
pimbrouwers

Ви можете перевірити, чи є 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', коли це було можливо.
Каспер Холдум


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

Виглядає досить компактно і елегантно. Однак рішення у прийнятій відповіді використовує ChangeType, сумісний з різними культурами, оскільки він приймає IFormatProvider. Оскільки для проекту це необхідність, я буду дотримуватися цього рішення.
Каспер Холдум 07

2

Можливо, ви можете передати, як параметр, делегат, який перетворить рядок на T.


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, який намагається прочитати в Т, але повертає 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;
    }

редагувати: тепер, коли я замислююся над цим, чи справді мені потрібно зробити тест convert.changetype? чи рядок as вже не намагається це зробити? Я не впевнений, що виконання цього додаткового виклику зміни типу насправді щось робить. Насправді це може просто збільшити накладні витрати на обробку шляхом генерування винятків. Якщо хтось знає про різницю, через яку це варто робити, будь ласка, напишіть!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.