Как сериализовать TimeSpan в XML - PullRequest
198 голосов
/ 12 марта 2009

Я пытаюсь сериализовать объект .NET TimeSpan в XML, и он не работает. Быстрый Google предположил, что, хотя TimeSpan является сериализуемым, XmlCustomFormatter не предоставляет методов для преобразования TimeSpan объектов в и из 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?

Ответы [ 12 ]

95 голосов
/ 18 июля 2011

Это лишь небольшая модификация подхода, предложенного в вопросе, но эта проблема 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); 
    }
}

Это будет сериализовать TimeSpan 0:02:45 как:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

В качестве альтернативы DataContractSerializer поддерживает TimeSpan.

69 голосов
/ 12 марта 2009

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

В качестве отступления я часто добавляю:

[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]

Это просто скрывает его в пользовательском интерфейсе и в ссылках на библиотеки DLL, чтобы избежать путаницы.

28 голосов
/ 06 сентября 2011

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

например:

protected TimeSpan myTimeout;
public string MyTimeout 
{ 
    get { return myTimeout.ToString(); } 
    set { myTimeout = TimeSpan.Parse(value); }
}

Это нормально, если значение свойства в основном используется в содержащем или наследующем классе и загружается из конфигурации xml.

Другие предложенные решения лучше, если вы хотите, чтобы общедоступное свойство было пригодным значением TimeSpan для других классов.

22 голосов
/ 03 декабря 2012

Объединяя ответ от Цветная сериализация и этого оригинального решения (что само по себе здорово), я получил это решение:

[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); }
    }
}
9 голосов
/ 09 декабря 2010

Вы можете создать легкую оболочку для структуры 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>
8 голосов
/ 12 марта 2009

Более читаемым вариантом будет сериализация в виде строки и использование метода TimeSpan.Parse для десериализации. Вы можете сделать то же самое, что и в вашем примере, но используя TimeSpan.ToString() в геттере и TimeSpan.Parse(value) в сеттере.

2 голосов
/ 10 июля 2009

Другой вариант - сериализация с использованием класса SoapFormatter, а не класса XmlSerializer.

Полученный XML-файл выглядит немного иначе ... некоторые теги с префиксом SOAP и т. Д., Но он может это сделать.

Вот что SoapFormatter сериализовало за 20 часов и 28 минут:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

Чтобы использовать класс SOAPFormatter, необходимо добавить ссылку на System.Runtime.Serialization.Formatters.Soap и использовать пространство имен с тем же именем.

1 голос
/ 25 мая 2015

Timespan хранится в xml как количество секунд, но, я надеюсь, его легко принять. Временной интервал, сериализованный вручную (реализующий 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

Посмотрите на Settings.cs. И есть некоторый хитрый код для использования XmlElementAttribute.

1 голос
/ 14 февраля 2014

Моя версия решения:)

[DataMember, XmlIgnore]
public TimeSpan MyTimeoutValue { get; set; }
[DataMember]
public string MyTimeout
{
    get { return MyTimeoutValue.ToString(); }
    set { MyTimeoutValue = TimeSpan.Parse(value); }
}

Редактировать: при условии, что это можно обнулять ...

[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;
    }
}
0 голосов
/ 21 сентября 2017

Если вы не хотите обходных путей, используйте класс DataContractSerializer из System.Runtime.Serialization.dll.

        using (var fs = new FileStream("file.xml", FileMode.Create))
        {
            var serializer = new DataContractSerializer(typeof(List<SomeType>));
            serializer.WriteObject(fs, _items);
        }
...