#c# #type-conversion
#c# #преобразование типов
Вопрос:
Сейчас я пишу универсальный метод для загрузки конфигурационных данных из XML. Множество параметров в моем случае хранятся в атрибутах узла, поэтому я решил написать универсальный метод для чтения атрибутов:
private static T ReadAttribute<T>(XElement Element,string AttributeName)
{
var attrib = Element.Attribute(AttributeName);
if (attrib != null)
{
return attrib.Value; // off cource error is in this line !
}
else
{
return default(T);
}
}
Этот метод должен попытаться прочитать атрибут с указанным именем, и если этот атрибут пропущен, он должен вернуть значение по умолчанию для типа атрибута. Тип атрибута определяется с помощью T.
Как показано в комментарии выше, моя проблема в том, что я не могу универсально преобразовать строковое значение в определенный тип. На самом деле я планирую использовать int, double и два перечисляемых типа как T.
Как мне следует действовать в этой ситуации? Как я должен преобразовать строковое значение в тип T?
Заранее спасибо!
Ответ №1:
Вы можете использовать Convert.ChangeType
. Это делает в основном то, что вы хотите. Но это преобразование, а не приведение, ну, не просто приведение.
return (T)Convert.ChangeType(attrib.Value, typeof(T), CultureInfo.InvariantCulture);
Причина, по которой вы можете просто привести строку к некоторому произвольному типу, заключается в том, что система типов этого не допускает. Однако Convert.ChangeType
возвращает объект, который может быть любого типа, и поэтому приведение разрешено.
CultureInfo.InvariantCulture
Важно, потому что содержимое XML не должно кодироваться / декодироваться с использованием разных языков. При работе с XML следует использовать класс XmlConvert, однако у него нет удобного универсального метода, подобного XmlConvert.ChangeType
.
XAttribute
Класс имеет множество явных пользовательских приведений, которые сопоставляются классу XmlConvert. Однако вы не можете просто использовать их с неограниченным параметром типа T и ожидать того же результата.
Что еще хуже, XML и Convert на самом деле не очень хорошо работают. Итак, если вы действительно серьезно относитесь к этому, вы бы написали что-то вроде этого для обработки преобразований.
static T ConvertTo<T>(XAttribute attr)
{
object value;
switch (Type.GetTypeCode(typeof(T)))
{
case TypeCode.Boolean: value = XmlConvert.ToBoolean(attr.Value); break;
case TypeCode.Int32: value = XmlConvert.ToInt32(attr.Value); break;
case TypeCode.DateTime: value = XmlConvert.ToDateTime(attr.Value); break;
// Add support for additional TypeCode values here...
default:
throw new ArgumentException(string.Format("Unsupported destination type '{0}'.", typeof(T)));
}
return (T)value;
}
Комментарии:
1. Для тех, кому интересно, конвертируйте. Для изменения типа требуется, чтобы исходный объект реализовал IConvertible (если только источник уже не является требуемым типом).
2. Существует ли при этом опасность того, что прямое преобразование было выполнено с помощью XAttribute (с помощью методов из XmlConvert), но обратное преобразование будет выполнено с помощью Convert? Я не знаю, есть ли угловые варианты, например, для DateTime, где преобразование может отличаться?
3. Да! Пока это работает идеально. Спасибо! Но как я могу быть уверен, что, например, я не могу использовать свой метод с гипотетическим ClassA, для которого не существует преобразования из string. Насколько я понимаю, неправильно просто указывать
where T: IConvertible
, это не дало бы достаточных результатов4. @Уилл Дин — Да, есть. Использование
Convert.ChangeType
будет работать в большинстве случаев, однакоXmlConvert
класс выполняет определенные действия, которые не выполняютсяConvert
классом. Было бы лучше создать свой собственныйConvertTo<T>
, который правильно работает с XML-контентом.5. @Anton Semenov Вы действительно не можете, это вызовет исключение во время выполнения, если это невозможно. Плюсом этого является то, что это довольно аккуратный синтаксис, но у него есть свои подводные камни.
Ответ №2:
Я бы выбрал TypeConverter. По сути, это класс, который выполняет преобразования в / из значений и языков. Основное различие между преобразователем типов и Convert.Тип изменения заключается в том, что для более позднего варианта требуется интерфейс IConvertible для исходного типа, в то время как преобразователи типов могут работать с любыми объектами.
Я создал вспомогательный класс для этого, поскольку я часто храню различные объекты конфигурации в xml-файлах. Вот почему также жестко запрограммировано преобразование в / из CultureInfo.Инвариантная культура.
public static class TypeConversion {
public static Object Convert(Object source, Type targetType) {
var sourceType = source.GetType();
if (targetType.IsAssignableFrom(sourceType))
return source;
var sourceConverter = TypeDescriptor.GetConverter(source);
if (sourceConverter.CanConvertTo(targetType))
return sourceConverter.ConvertTo(null, CultureInfo.InvariantCulture, source, targetType);
var targetConverter = TypeDescriptor.GetConverter(targetType);
if (targetConverter.CanConvertFrom(sourceType))
return targetConverter.ConvertFrom(null, CultureInfo.InvariantCulture, source);
throw new ArgumentException("Neither the source nor the target has a TypeConverter that supports the requested conversion.");
}
public static TTarget Convert<TTarget>(object source) {
return (TTarget)Convert(source, typeof(TTarget));
}
}
Вполне возможно создать свой собственный TypeConverter для обработки системных типов, таких как System.Version (который не реализует IConvertible), для поддержки преобразований, например, из строк, содержащих номер версии («a.b.c.d»), в объект фактической версии.
public class VersionTypeConverter : TypeConverter {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
var s = value as string;
if (s != null)
return new Version(s);
return base.ConvertFrom(context, culture, value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
if (destinationType == typeof(string))
return true;
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
var v = value as Version;
if (v != null amp;amp; destinationType == typeof(string)) {
return v.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
Для фактического использования этого поставщика вам необходимо зарегистрировать его во время запуска приложения, используя TypeDescriptor.addProvider, передающий пользовательский TypeDescriptionProvider, и typeof(Version)
. Для этого необходимо вернуть пользовательский CustomTypeDescriptor в TypeDescriptorProvider.Метод GetTypeDescriptor и дескриптор должны переопределить GetConverter, чтобы вернуть новый экземпляр VersionTypeConverter. Просто. 😉
Комментарии:
1. Вау! Это действительно мощное решение. Теперь я пытаюсь использовать это в своей программе. Спасибо!
2. Я полагаю, что все системные типы, реализующие IConvertible, также уже имеют TypeConverter. Основная причина, по которой требуется так много кода, заключается в том, что материал TypeDescriptor оборачивает всю структуру отражения. Переопределить CustomTypeDescriptor. Получите свойства для добавления / удаления свойств и людей, использующих TypeDescriptor. GetProperties увидит новые материалы. Вы можете создавать свои собственные свойства и обрабатывать их хранение самостоятельно, если хотите.
3. @SimonSvensson очень удобно, и святые старые сообщения batman, но есть ли причина, по которой вы пытаетесь ConvertTo, прежде чем пытаться ConvertFrom, и если да, не могли бы вы добавить это к своему (теперь 3-летнему) ответу? Для моего варианта использования, я полагаю, мне понадобится ConvertFrom чаще, чем ConvertTo, поскольку я работаю с пользовательским ORM, Который преобразует целые числа, строки и т.д. Из базы данных в известные пользовательские типы; конвертер int. CanConvertTo() всегда будет возвращать false, и чтение происходит намного чаще, чем запись, поэтому я собираюсь изменить этот порядок, если я чего-то не упустил (кроме того, что не пишите / не используйте пользовательский ORM)
4. @Darmon, никогда особо не задумывался об этом. Проблем возникнуть не должно, если только у вас не возникнет редких случаев, когда как ConvertTo исходного конвертера, так и ConvertFrom целевого конвертера утверждают, что они могут конвертировать в другой тип, и они отличаются реализацией. Несколько надуманный пример, и я не могу придумать ничего встроенного, что могло бы это сделать.
5. @SimonSvensson Да, похоже, что это было бы не только редкостью, но и, вероятно, ошибкой в реализации; по крайней мере, с любым из странных случаев, которые я могу придумать! 😀 Я не был уверен, что вы реализовали в таком порядке из-за производительности или, возможно, из-за соглашения о вызове
ConvertTo()
раньшеConvertFrom()
. Спасибо за ответ, я собираюсь начать сConvertFrom()
первого, потому что я буду использовать это чаще, так что это исключит несколько первых «если». Это не должно иметь большого значения, но должно быть немного менее расточительным.
Ответ №3:
Встроенные методы не помогут, если T является типом, определенным самостоятельно. Допустим, xml выглядит следующим образом:
//some other segments
<Book Name="Good book" Price="20" Author="Jack" />
И вы T — это учебник, который выглядит как:
class Book
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Author { get; set; }
//maybe some other properties
}
Не существует волшебства для автоматического преобразования XElement
в экземпляр Book
, вам нужно реализовать это самостоятельно. Простая и общая реализация выглядит примерно так:
interface IXElementConvertible
{
void LoadFrom(XElement element);
}
class Book : IXElementConvertible
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Author { get; set; }
public void LoadFrom(XElement element)
{
this.Name = element.Attribute("Name").Value;
//blabla
}
}
И вам нужно изменить свой метод:
private static T ReadAttribute<T>(XElement Element,string AttributeName)
where T : IXElementConvertible, new()
{
T t = new T();
t.LoadFrom(element);
//just an example here, not the complete implementation
}
Ответ №4:
Я думаю, вам следует выполнить следующую проверку в вашем коде:
if (attrib.Value is T)
{
...
}
Комментарии:
1. Это работает следующим образом @Dummy01. Если вы ошибаетесь, вы получите голосование «против», потому что мы не хотим продвигать неправильные ответы. Однако, если вы искренни и не хотите узнать больше, я бы с радостью помог вам с этим. Ваше предложение фактически полностью игнорирует правила системы типов и не имеет никакого смысла.
Value
Свойство имеет типSystem.String
, оно никогда не будет вводить test против,T
еслиT
это не строка. Что я не вижу смысла делать, это не решает проблему, и мне ясно, что вы не поняли вопрос.2. Вы потратили слишком много строк, чтобы сделать из меня учителя. Я думаю, это идеально подходит для вашего прямого отрицательного голосования. Но что касается истории, да, вы правы. Я ошибочно помнил, что значение является объектом, а не строкой, и именно отсюда исходила моя проверка. На самом деле, если вы заметили, что я только что сказал, что сделал неправильно, это самое простое решение вашей проблемы, и в этом вы также воспользуетесь моей проверкой. Но я не рискую еще одним плохим голосованием учителя, так что, думаю, вам придется разобраться с этим самостоятельно 🙂