So serialisieren Sie einen TimeSpan in XML


206

Ich versuche, ein .NET- TimeSpanObjekt in XML zu serialisieren , und es funktioniert nicht. Eine schnelle Google hat vorgeschlagen, dass es zwar TimeSpanserialisierbar ist, XmlCustomFormatteraber keine Methoden zum Konvertieren von TimeSpanObjekten in und aus XML bietet .

Ein vorgeschlagener Ansatz bestand darin, das TimeSpanfür die Serialisierung zu ignorieren und stattdessen das Ergebnis von TimeSpan.Ticks(und die Verwendung new TimeSpan(ticks)für die Deserialisierung) zu serialisieren . Ein Beispiel hierfür folgt:

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

Während dies in meinen kurzen Tests zu funktionieren scheint - ist dies der beste Weg, um dies zu erreichen?

Gibt es eine bessere Möglichkeit, eine TimeSpan von und nach XML zu serialisieren?


4
Die Antwort von Rory MacLeod unten ist tatsächlich die Art und Weise, wie Microsoft dies empfiehlt.
Jeff

2
Ich würde für TimeSpand keine langen Ticks verwenden, da der Dauertyp von XML genau übereinstimmt. Das Problem wurde im Jahr 2008 bei Microsoft angesprochen, aber nie behoben. Damals
Kenneth Xu

Antworten:


71

Die Art und Weise, wie Sie bereits gepostet haben, ist wahrscheinlich die sauberste. Wenn Ihnen die zusätzliche Eigenschaft nicht gefällt, können Sie sie implementieren IXmlSerializable, aber dann müssen Sie alles tun , was den Punkt weitgehend zunichte macht. Ich würde gerne den Ansatz verwenden, den Sie veröffentlicht haben. Es ist (zum Beispiel) effizient (kein komplexes Parsen usw.), kulturunabhängige, eindeutige und zeitstempelartige Zahlen sind leicht und allgemein zu verstehen.

Nebenbei füge ich oft hinzu:

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

Dies verbirgt es nur in der Benutzeroberfläche und beim Verweisen auf DLLs, um Verwirrung zu vermeiden.


5
Es ist nicht so schlimm, alles zu tun, wenn Sie die Schnittstelle in einer Struktur implementieren, die System.TimeSpan umschließt, anstatt sie in MyClass zu implementieren. Dann müssen Sie nur noch den Typ in Ihrer MyClass.TimeSinceLastEvent-Eigenschaft
ändern

103

Dies ist nur eine geringfügige Änderung des in der Frage vorgeschlagenen Ansatzes. In diesem Microsoft Connect-Problem wird jedoch empfohlen, eine Eigenschaft für die Serialisierung wie folgt zu verwenden:

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

Dies würde eine Zeitspanne von 0:02:45 serialisieren als:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Alternativ DataContractSerializerunterstützt das TimeSpan.


15
+1 für XmlConvert.ToTimeSpan (). Es behandelt die Syntax der ISO-Standarddauer für Zeiträume wie PT2H15M, siehe en.wikipedia.org/wiki/ISO_8601#Durations
yzorg

2
Korrigieren Sie mich, wenn ich falsch liege, aber die serlisierte Zeitspanne "PT2M45S" ist 00:02:45, nicht 2:45:00.
Tom Pažourek

Der Verbindungslink ist jetzt defekt. Vielleicht kann er durch diesen ersetzt werden: connect.microsoft.com/VisualStudio/feedback/details/684819/… ? Die Technik sieht auch ein bisschen anders aus ...
TJB

Eine seltsame Frage, die wir beantworten müssen: Haben wir eine Möglichkeit, diesen Wert PT2M45S in SQL auf Zeit zu deserialisieren?
Xander

28

In einigen Fällen kann dies funktionieren, indem Sie Ihrer öffentlichen Eigenschaft ein Hintergrundfeld zuweisen, bei dem es sich um eine Zeitspanne handelt. Die öffentliche Eigenschaft wird jedoch als Zeichenfolge angezeigt.

z.B:

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

Dies ist in Ordnung, wenn der Eigenschaftswert hauptsächlich in der enthaltenden Klasse oder in den ererbenden Klassen verwendet und aus der XML-Konfiguration geladen wird.

Die anderen vorgeschlagenen Lösungen sind besser, wenn die öffentliche Eigenschaft ein verwendbarer TimeSpan-Wert für andere Klassen sein soll.


Mit Abstand die einfachste Lösung. Ich habe mir genau das Gleiche ausgedacht und es funktioniert wie ein Zauber. Einfach zu implementieren und zu verstehen.
wpfwannabe

1
Dies ist hier die beste Lösung. Es serialisiert sehr gut !!! Vielen Dank für Ihre Eingabe Freund!
Entwickler

25

Durch die Kombination einer Antwort aus der Farbserialisierung und dieser ursprünglichen Lösung (die für sich genommen großartig ist) habe ich folgende Lösung erhalten:

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

wo XmlTimeSpanKlasse so ist:

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

Der beste und einfachste Weg, um dieses Problem zu lösen ... für mich
Moraru Viorel

das ist absolut genial - ich bin super beeindruckt!
Jim

9

Sie können einen Light Wrapper um die TimeSpan-Struktur erstellen:

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

Beispiel für ein serialisiertes Ergebnis:

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

Irgendeine Idee, wie man die Ausgabe als XmlAttribute macht?
ala

@ala, Wenn ich Ihre Frage richtig verstehe, besteht die Antwort darin, das XmlAttributeAttribute auf die Eigenschaft anzuwenden, die Sie als Attribut ausdrücken möchten. Das gilt natürlich nicht speziell für TimeSpan.
Phoog

+1 Schön, außer ich würde es nicht als String serialisieren, sondern Ticksso lange.
ChrisWue

@ChrisWue In meinem Büro verwenden wir die XML-Serialisierung, wenn wir eine lesbare Ausgabe wünschen. Eine lange Zeitspanne zu serialisieren ist mit diesem Ziel nicht ganz kompatibel. Wenn Sie die XML-Serialisierung aus einem anderen Grund verwenden, ist die Serialisierung der Ticks natürlich möglicherweise sinnvoller.
Phoog

8

Eine besser lesbare Option wäre, als Zeichenfolge zu serialisieren und die TimeSpan.ParseMethode zum Deserialisieren zu verwenden. Sie können das Gleiche wie in Ihrem Beispiel tun, jedoch TimeSpan.ToString()im Getter und TimeSpan.Parse(value)im Setter verwenden.


2

Eine andere Möglichkeit wäre, es mit der SoapFormatterKlasse und nicht mit der zu serialisierenXmlSerializer Klasse .

Die resultierende XML-Datei sieht etwas anders aus ... einige "SOAP" -präfixierte Tags usw. ... aber sie kann es.

Hier ist , was SoapFormattereine Zeitspanne von 20 Stunden serialisiert und 28 Minuten serialisiert:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

Um die SOAPFormatter-Klasse zu verwenden, müssen Sie einen Verweis auf System.Runtime.Serialization.Formatters.Soapden gleichnamigen Namespace hinzufügen und diesen verwenden.


Dies ist, wie es in .net 4.0
Kirk Broadhurst

1

Meine Version der Lösung :)

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

Bearbeiten: vorausgesetzt, es ist nullbar ...

[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

Zeitspanne in XML als Anzahl von Sekunden gespeichert, aber es ist einfach zu übernehmen, hoffe ich. Manuell serialisierte Zeitspanne (Implementierung von 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));
            }
       }
    }
}

Es gibt ein umfassenderes Beispiel: https://bitbucket.org/njkazakov/timespan-serialization

Schauen Sie sich Settings.cs an. Und es gibt einen kniffligen Code, um XmlElementAttribute zu verwenden.


1
Bitte zitieren Sie die relevanten Informationen von diesem Link. Alle für Ihre Antwort erforderlichen Informationen sollten sich auf dieser Website befinden. Anschließend können Sie diesen Link als Quelle angeben.
SuperBiasedMan

0

Für die Serialisierung von Datenverträgen verwende ich Folgendes.

  • Wenn Sie die serialisierte Eigenschaft privat halten, bleibt die öffentliche Schnittstelle sauber.
  • Durch die Verwendung des öffentlichen Eigenschaftsnamens für die Serialisierung bleibt das XML sauber.
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

Wenn Sie keine Problemumgehungen wünschen, verwenden Sie die DataContractSerializer-Klasse aus 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

Versuche dies :

//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); }
        }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.