Как сериализовать TimeSpan в XML
Я пытаюсь сериализовать .NET TimeSpan
объект в XML и он не работает. Быстрый гугл подсказал, что в то время как TimeSpan
сериализуется, то XmlCustomFormatter
не предоставляет методы для преобразования TimeSpan
объекты в XML и из XML.
один из предложенных подходов состоял в том, чтобы игнорировать TimeSpan
для сериализации, а вместо этого сериализовать результат TimeSpan.Ticks
(и использовать new TimeSpan(ticks)
для десериализации). Пример этого выглядит следующим образом:
[Serializable]
public class MyClass
{
// Local Variable
private TimeSpan m_TimeSinceLastEvent;
// Public Property - XmlIgnore as it doesn't serialize anyway
[XmlIgnore]
public TimeSpan TimeSinceLastEvent
{
get { return m_TimeSinceLastEvent; }
set { m_TimeSinceLastEvent = value; }
}
// Pretend property for serialization
[XmlElement("TimeSinceLastEvent")]
public long TimeSinceLastEventTicks
{
get { return m_TimeSinceLastEvent.Ticks; }
set { m_TimeSinceLastEvent = new TimeSpan(value); }
}
}
в то время как это, кажется, работает в моем краткое тестирование-это лучший способ достичь этого?
есть ли лучший способ сериализации TimeSpan в XML и из XML?
13 ответов:
то, как вы уже разместили, вероятно, самый чистый. Если вам не нравится дополнительное свойство, вы можете реализовать
IXmlSerializable
, но тогда вы должны сделать все, что в значительной степени побеждает точку. Я бы с удовольствием использовал подход, который вы опубликовали; он (например) эффективен (без сложного разбора и т. д.), независим от культуры, однозначен, а номера типа временных меток легко и обычно понятны.в сторону, я часто добавляю:
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
Это просто скрывает его в пользовательском интерфейсе и в ссылках на DLL, чтобы избежать путаницы.
Это лишь небольшая модификация подхода, предложенного в вопросе, но эта проблема Microsoft Connect рекомендует использовать свойство для сериализации следующим образом:
[XmlIgnore] public TimeSpan TimeSinceLastEvent { get { return m_TimeSinceLastEvent; } set { m_TimeSinceLastEvent = value; } } // XmlSerializer does not support TimeSpan, so use this property for // serialization instead. [Browsable(false)] [XmlElement(DataType="duration", ElementName="TimeSinceLastEvent")] public string TimeSinceLastEventString { get { return XmlConvert.ToString(TimeSinceLastEvent); } set { TimeSinceLastEvent = string.IsNullOrEmpty(value) ? TimeSpan.Zero : XmlConvert.ToTimeSpan(value); } }
это будет сериализовать промежуток времени 0: 02: 45 как:
<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>
кроме того,
DataContractSerializer
поддерживает TimeSpan.
что-то, что может работать в некоторых случаях, - это дать вашему публичному свойству резервное поле, которое является TimeSpan, но публичное свойство предоставляется как строка.
например:
protected TimeSpan myTimeout; public string MyTimeout { get { return myTimeout.ToString(); } set { myTimeout = TimeSpan.Parse(value); } }
это нормально, если значение свойства используется в основном в содержащем классе или наследующих классах и загружается из конфигурации xml.
другие предлагаемые решения лучше, если вы хотите, чтобы публичное свойство было полезным значением TimeSpan для других классов.
объединение ответа от цвета сериализации и это оригинальное решение (что само по себе здорово) я получил это решение:
[XmlElement(Type = typeof(XmlTimeSpan))] public TimeSpan TimeSinceLastEvent { get; set; }
здесь
XmlTimeSpan
класс, как это:public class XmlTimeSpan { private const long TICKS_PER_MS = TimeSpan.TicksPerMillisecond; private TimeSpan m_value = TimeSpan.Zero; public XmlTimeSpan() { } public XmlTimeSpan(TimeSpan source) { m_value = source; } public static implicit operator TimeSpan?(XmlTimeSpan o) { return o == null ? default(TimeSpan?) : o.m_value; } public static implicit operator XmlTimeSpan(TimeSpan? o) { return o == null ? null : new XmlTimeSpan(o.Value); } public static implicit operator TimeSpan(XmlTimeSpan o) { return o == null ? default(TimeSpan) : o.m_value; } public static implicit operator XmlTimeSpan(TimeSpan o) { return o == default(TimeSpan) ? null : new XmlTimeSpan(o); } [XmlText] public long Default { get { return m_value.Ticks / TICKS_PER_MS; } set { m_value = new TimeSpan(value * TICKS_PER_MS); } } }
вы можете создать легкую обертку вокруг структуры TimeSpan:
namespace My.XmlSerialization { public struct TimeSpan : IXmlSerializable { private System.TimeSpan _value; public static implicit operator TimeSpan(System.TimeSpan value) { return new TimeSpan { _value = value }; } public static implicit operator System.TimeSpan(TimeSpan value) { return value._value; } public XmlSchema GetSchema() { return null; } public void ReadXml(XmlReader reader) { _value = System.TimeSpan.Parse(reader.ReadContentAsString()); } public void WriteXml(XmlWriter writer) { writer.WriteValue(_value.ToString()); } } }
образец сериализованный результат:
<Entry> <StartTime>2010-12-06T08:45:12.5</StartTime> <Duration>2.08:29:35.2500000</Duration> </Entry>
более читаемым вариантом было бы сериализовать в виде строки и использовать
TimeSpan.Parse
метод десериализации. Вы можете сделать то же самое, что и в вашем примере, но с помощьюTimeSpan.ToString()
в геттере иTimeSpan.Parse(value)
в сеттер.
другой вариант-сериализовать его с помощью
SoapFormatter
класса, а неXmlSerializer
класса.полученный XML-файл выглядит немного по-другому...некоторые "мыло"-префикс теги и т. д...но он может это сделать.
вот что
SoapFormatter
сериализован интервал времени 20 часов и 28 минут сериализован в:<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>
чтобы использовать класс SOAPFormatter, нужно добавить ссылку на
System.Runtime.Serialization.Formatters.Soap
и использовать пространство имен с тем же названием.
моя версия решения :)
[DataMember, XmlIgnore] public TimeSpan MyTimeoutValue { get; set; } [DataMember] public string MyTimeout { get { return MyTimeoutValue.ToString(); } set { MyTimeoutValue = TimeSpan.Parse(value); } }
Edit: предполагая, что он обнуляется...
[DataMember, XmlIgnore] public TimeSpan? MyTimeoutValue { get; set; } [DataMember] public string MyTimeout { get { if (MyTimeoutValue != null) return MyTimeoutValue.ToString(); return null; } set { TimeSpan outValue; if (TimeSpan.TryParse(value, out outValue)) MyTimeoutValue = outValue; else MyTimeoutValue = null; } }
Timespan хранится в xml как количество секунд, но это легко принять, я надеюсь. TimeSpan сериализуется вручную (реализация IXmlSerializable):
public class Settings : IXmlSerializable { [XmlElement("IntervalInSeconds")] public TimeSpan Interval; public XmlSchema GetSchema() { return null; } public void WriteXml(XmlWriter writer) { writer.WriteElementString("IntervalInSeconds", ((int)Interval.TotalSeconds).ToString()); } public void ReadXml(XmlReader reader) { string element = null; while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) element = reader.Name; else if (reader.NodeType == XmlNodeType.Text) { if (element == "IntervalInSeconds") Interval = TimeSpan.FromSeconds(double.Parse(reader.Value.Replace(',', '.'), CultureInfo.InvariantCulture)); } } } }
есть более полный пример: https://bitbucket.org/njkazakov/timespan-serialization
посмотреть настройки.цезий. И есть какой-то сложный код для использования XmlElementAttribute.
для сериализации контракта данных я использую следующий.
- сохранение сериализованного свойства private сохраняет открытый интерфейс чистым.
- использование имени общедоступного свойства для сериализации сохраняет XML чистым.
Public Property Duration As TimeSpan <DataMember(Name:="Duration")> Private Property DurationString As String Get Return Duration.ToString End Get Set(value As String) Duration = TimeSpan.Parse(value) End Set End Property
не могу комментировать или ранжировать, но комментарий SoapDuration
[XmlElement, Type=SoapDuration] public TimeSpan TimeSinceLastEvent
или
public SoapDuration TimeSinceLastEventTicks
Если вы не хотите никаких обходных путей, используйте класс DataContractSerializer из System.Во время выполнения.Сериализация.файл DLL.
using (var fs = new FileStream("file.xml", FileMode.Create)) { var serializer = new DataContractSerializer(typeof(List<SomeType>)); serializer.WriteObject(fs, _items); }