Как сериализовать TimeSpan в XML

206

Я пытаюсь сериализовать TimeSpanобъект .NET в 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?

joeldixon66
источник
4
Ответ Рори МакЛауда, приведенный ниже, на самом деле является способом, которым Microsoft рекомендует сделать это.
Джефф
2
Я бы не использовал длинные тики для TimeSpand, потому что тип длительности XML - точное совпадение. Эта проблема была поднята в Microsoft в 2008 году, но так и не была решена. В то время был задокументирован обходной путь: kennethxu.blogspot.com/2008/09/…
Кеннет Сюй

Ответы:

71

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

Кроме того, я часто добавляю:

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

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

Марк Гравелл
источник
5
Делать все не так уж и плохо, если вы реализуете интерфейс в структуре, которая обертывает System.TimeSpan, а не реализуете ее в MyClass. Тогда вам нужно только изменить тип в свойстве
MyClass.TimeSinceLastEvent
103

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

Рори Маклеод
источник
15
+1 для XmlConvert.ToTimeSpan (). Он обрабатывает стандартный синтаксис продолжительности ISO для промежутка времени , как PT2H15M см en.wikipedia.org/wiki/ISO_8601#Durations
yzorg
2
Поправьте меня, если я ошибаюсь, но период времени "PT2M45S", указанный в программе, - 00:02:45, а не 2:45:00.
Том Пажурек
Ссылка на соединение теперь не работает, может быть, ее можно заменить на эту: connect.microsoft.com/VisualStudio/feedback/details/684819/… ? Техника также выглядит немного по-другому ...
TJB
Странный вопрос для продолжения, есть ли у нас способ десериализации этого значения PT2M45S для времени в SQL?
Ксандер
28

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

например:

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

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

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

Wes
источник
Безусловно самое простое решение. Я придумал точно то же самое, и это работает как шарм. Легко реализовать и понять.
wpfwannabe
1
Это лучшее решение здесь. Сериализуется очень хорошо !!! Спасибо за ваш вклад друг!
Разработчик
25

Комбинируя ответ из сериализации Color и это оригинальное решение (которое само по себе прекрасно), я получил это решение:

[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

Вы можете создать легкую оболочку вокруг структуры 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>
phoog
источник
Любая идея, как сделать вывод как XmlAttribute?
аля
@ala, Если я правильно понимаю ваш вопрос, ответ заключается в применении XmlAttributeAttribute к свойству, которое вы хотите выразить в качестве атрибута. Конечно, это не относится к TimeSpan.
фото
+1 Хорошо, только я бы не сериализовал его как строку, но Ticksкак долго.
ChrisWue
@ChrisWue В моем офисе мы используем сериализацию xml, когда мы хотим, чтобы вывод читался человеком; сериализация временного интервала как длинного не совсем совместима с этой целью. Если вы используете сериализацию xml по другой причине, конечно, сериализация тиков может иметь больше смысла.
фото
8

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

Руна Гримстад
источник
2

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

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

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

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

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

Liam
источник
Вот как он сериализуется в .net 4.0
Кирк Бродхерст
1

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

[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;
    }
}
Gildor
источник
1

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

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

askazakov
источник
1
Пожалуйста, указывайте соответствующую информацию по этой ссылке. Вся информация, необходимая для вашего ответа, должна быть на этом сайте, и тогда вы можете привести эту ссылку в качестве источника.
SuperBiasedMan
0

Для сериализации контракта данных я использую следующее.

  • Хранение сериализованного свойства в тайне делает общедоступный интерфейс чистым.
  • Использование имени открытого свойства для сериализации обеспечивает чистоту 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
JRS
источник
0

Если вы не хотите обходных путей, используйте класс 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);
        }
Саша якобчук
источник
-2

Попробуй это :

//Don't Serialize Time Span object.
        [XmlIgnore]
        public TimeSpan m_timeSpan;
//Instead serialize (long)Ticks and instantiate Timespan at time of deserialization.
        public long m_TimeSpanTicks
        {
            get { return m_timeSpan.Ticks; }
            set { m_timeSpan = new TimeSpan(value); }
        }
Manvendra
источник