Jak serializować TimeSpan do XML


206

Próbuję serializować TimeSpanobiekt .NET do XML i nie działa. Szybki google zasugerował, że chociaż TimeSpanmożna go serializować, XmlCustomFormatternie zapewnia on metod konwertowania TimeSpanobiektów na i z XML.

Jednym sugerowanym podejściem było zignorowanie TimeSpanserializacji i zamiast tego serializacja wyniku TimeSpan.Ticks(i użycie new TimeSpan(ticks)do deserializacji). Oto przykład:

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

Chociaż wydaje się, że działa to w moich krótkich testach - czy to najlepszy sposób na osiągnięcie tego?

Czy istnieje lepszy sposób na serializację TimeSpan do iz XML?


4
Odpowiedź Rory'ego MacLeoda poniżej jest w rzeczywistości sposobem, w jaki Microsoft zaleca to zrobić.
— Jeff

2
Nie użyłbym długich znaczników dla TimeSpand, ponieważ typ czasu trwania XML jest dokładnie taki sam. Problem został podniesiony do firmy Microsoft w roku 2008, ale nigdy nie został rozwiązany. Udokumentowano wówczas obejście: kennethxu.blogspot.com/2008/09/…
— Kenneth Xu

Odpowiedzi:


71

Sposób, w jaki już pisałeś, jest prawdopodobnie najczystszy. Jeśli nie podoba ci się dodatkowa właściwość, możesz ją wdrożyć IXmlSerializable, ale wtedy musisz zrobić wszystko , co w dużej mierze pokonuje sens. Z chęcią skorzystam z opublikowanego przez ciebie podejścia; jest (na przykład) wydajny (bez złożonej analizy itp.), liczby niezależne od kultury, jednoznaczne, a numery typu datownika są łatwe i powszechnie rozumiane.

Nawiasem mówiąc, często dodaję:

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

To tylko ukrywa go w interfejsie użytkownika i odwołuje się do bibliotek dll, aby uniknąć nieporozumień.


5
Robienie wszystkiego nie jest takie złe, jeśli zaimplementujesz interfejs na strukturze, która otacza System.TimeSpan, zamiast implementować go na MyClass. Następnie musisz tylko zmienić typ we właściwości
— MyClass.TimeSinceLastEvent

103

To tylko niewielka modyfikacja podejścia zaproponowanego w pytaniu, ale ten problem z Microsoft Connect zaleca użycie właściwości do serializacji takiej jak ta:

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

Spowodowałoby to serializację TimeSpan 0:02:45 jako:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Alternatywnie DataContractSerializerobsługuje TimeSpan.


15
+1 dla XmlConvert.ToTimeSpan (). Obsługuje standardową składnię czasu trwania ISO dla przedziału czasu, takiego jak PT2H15M, patrz en.wikipedia.org/wiki/ISO_8601#Durations
— yzorg

2
Popraw mnie, jeśli się mylę, ale ząbkowany TimeSpan „PT2M45S” to 00:02:45, a nie 2:45:00.
— Tom Pažourek

Link do łączenia jest teraz zepsuty, może można go zastąpić tym: connect.microsoft.com/VisualStudio/feedback/details/684819/... ? Technika wygląda również trochę inaczej ...
— TJB

Dziwne pytanie, które należy podjąć, czy istnieje sposób na deserializację tej wartości PT2M45S na czas w SQL?
— Xander

28

Coś, co może działać w niektórych przypadkach, to nadanie swojej własności publicznej pola pomocniczego, które jest TimeSpan, ale własność publiczna jest ujawniona jako ciąg znaków.

na przykład:

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

Jest to w porządku, jeśli wartość właściwości jest używana głównie w / w klasie zawierającej lub dziedziczącej i jest ładowana z konfiguracji xml.

Inne proponowane rozwiązania są lepsze, jeśli chcesz, aby własność publiczna była użyteczną wartością TimeSpan dla innych klas.


Zdecydowanie najłatwiejsze rozwiązanie. Wymyśliłem dokładnie to samo i działa jak urok. Łatwy do wdrożenia i zrozumienia.
— wpfwannabe

1
To najlepsze rozwiązanie tutaj. Serializuje się bardzo dobrze !!! Dziękujemy za przesłanie znajomego!
— Deweloper

25

Łącząc odpowiedź z serializacji kolorów i tego oryginalnego rozwiązania (które samo w sobie jest świetne) otrzymałem to rozwiązanie:

[XmlElement(Type = typeof(XmlTimeSpan))]
public TimeSpan TimeSinceLastEvent { get; set; }

gdzie XmlTimeSpanklasa jest taka:

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

Najlepszy i łatwy sposób rozwiązania tego problemu ... dla mnie
— Moraru Viorel

to jest absolutnie genialne - jestem pod wielkim wrażeniem!
— Jim

9

Możesz stworzyć lekkie opakowanie wokół struktury 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());
        }
    }
}

Przykładowy wynik zserializowany:

<Entry>
  <StartTime>2010-12-06T08:45:12.5</StartTime>
  <Duration>2.08:29:35.2500000</Duration>
</Entry>

jakiś pomysł, jak zrobić wynik jako XmlAttribute?
— ala

@ala, jeśli dobrze rozumiem twoje pytanie, odpowiedzią jest zastosowanie XmlAttributeAttribute do właściwości, którą chcesz wyrazić jako atrybut. Oczywiście nie dotyczy to TimeSpan.
— phoog

+1 Fajnie, tyle że nie serializowałbym tego jako łańcucha, ale Tickstak długo.
— ChrisWue,

@ChrisWue W moim biurze używamy serializacji XML, gdy chcemy wydrukować czytelny dla człowieka; serializacja przedziału czasu jako długiego nie jest w pełni zgodna z tym celem. Jeśli używasz serializacji XML z innego powodu, oczywiście serializacja znaczników może być bardziej sensowna.
— phoog

8

Bardziej czytelną opcją byłoby serializowanie jako ciąg i użycie TimeSpan.Parsemetody do deserializacji. Możesz zrobić to samo, co w twoim przykładzie, ale używając TimeSpan.ToString()gettera i TimeSpan.Parse(value)setera.


2

Inną opcją byłoby serializowanie go za pomocą SoapFormatterklasy, a nie XmlSerializerklasy.

Wynikowy plik XML wygląda trochę inaczej ... niektóre tagi z prefiksem „SOAP” itp., Ale może to zrobić.

Oto, co SoapFormatterszeregowało przedział czasowy wynoszący 20 godzin i 28 minut, aby:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

Aby użyć klasy SOAPFormatter, należy dodać odwołanie do System.Runtime.Serialization.Formatters.Soapprzestrzeni nazw o tej samej nazwie i użyć jej.


Tak serializuje się w .net 4.0
— Kirk Broadhurst

1

Moja wersja rozwiązania :)

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

Edycja: zakładając, że jest zerowy ...

[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

Przedział czasu przechowywany w xml jako liczba sekund, ale mam nadzieję, że łatwo go przyjąć. Przedział czasu jest serializowany ręcznie (implementacja 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));
            }
       }
    }
}

Bardziej wyczerpujący przykład: https://bitbucket.org/njkazakov/timespan-serialization

Spójrz na Settings.cs. I jest jakiś trudny kod do użycia XmlElementAttribute.


1
Podaj odpowiednie informacje z tego linku. Wszystkie informacje potrzebne do udzielenia odpowiedzi powinny znajdować się na tej stronie, a następnie możesz podać ten link jako źródło.
— SuperBiasedMan

0

Do serializacji umów danych używam następujących.

  • Utrzymanie zserializowanej własności prywatnej utrzymuje interfejs publiczny w czystości.
  • Użycie nazwy właściwości publicznej do serializacji utrzymuje XML w czystości.
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

Jeśli nie chcesz żadnych obejść, użyj klasy DataContractSerializer z 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

Spróbuj tego :

//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); }
        }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.