Як серіалізувати TimeSpan до XML


206

Я намагаюся серіалізувати .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 і з нього?


4
Відповідь Рорі Маклеод нижче - насправді спосіб, яким Microsoft рекомендує це робити.
Джефф

2
Я б не використовував довгі кліщі для TimeSpand, оскільки тип тривалості XML - це точно збіг. Проблема була піднята Microsoft у 2008 році, але так і не була вирішена. Тоді було задокументовано обхідне рішення: kennethxu.blogspot.com/2008/09/…
Кеннет Сю

Відповіді:


71

Шлях, який ви вже розмістили, мабуть, найчистіший. Якщо вам не сподобається додаткова властивість, ви могли б реалізувати IXmlSerializable, але тоді вам доведеться зробити все , що значною мірою перемагає суть. Я б із задоволенням використовував підхід, який ви опублікували; це (наприклад) ефективне (без складного розбору тощо), незалежне від культури, однозначне число та цифри типу часових позначок легко та загальнозрозуміле.

Як осторонь я часто додаю:

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

Це просто приховує його в інтерфейсі та при посиланні на dlls, щоб уникнути плутанини.


5
Робити все не так вже й погано, якщо ви реалізуєте інтерфейс на структурі, яка завершує System.TimeSpan, а не реалізує його на MyClass. Тоді вам залишається лише змінити тип власності MyClass.TimeSinceLastEvent
foog

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

Це буде серіалізувати часовий простір 0:02:45 як:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Крім того, DataContractSerializerпідтримує TimeSpan.


15
+1 для XmlConvert.ToTimeSpan (). Він обробляє стандартний синтаксис тривалості ISO для проміжку часу , як PT2H15M см en.wikipedia.org/wiki/ISO_8601#Durations
yzorg

2
Виправте мене, якщо я помиляюся, але прокручений TimeSpan "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 значенням для інших класів.


На сьогодні найпростіше рішення. Я придумав абсолютно те саме, і це працює як шарм. Легкий у виконанні та розумінні.
wpfwannabe

1
Тут найкраще рішення. Це серіалізується дуже добре !!! Дякую за те, що ти знайомий!
Розробник

25

Поєднуючи відповідь із кольорової серіалізації та цього оригінального рішення (що само по собі чудово), я отримав таке рішення:

[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>

будь-яка ідея, як зробити вихід у вигляді XmlAttribute?
ала

@ala, Якщо я правильно розумію ваше запитання, відповідь - застосувати XmlAttributeAttribute до властивості, яке ви хочете висловити як атрибут. Звичайно, це не особливо для TimeSpan.
foog

+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та використати однойменний простір імен.


Ось як це серіалізується в .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;
    }
}

1

Час дії зберігається в 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
Будь ласка, цитуйте відповідну інформацію за цим посиланням. Вся інформація, необхідна для вашої відповіді, повинна знаходитись на цьому веб-сайті, і тоді ви можете вказати це посилання як джерело.
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

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