Comment sérialiser un TimeSpan en XML

J'essaie de sérialiser un objet. net TimeSpan en XML et cela ne fonctionne pas. Un rapide google a suggéré que si TimeSpan est sérialisable, le XmlCustomFormatter ne fournit pas de méthodes pour convertir les objets TimeSpan vers et depuis XML.

Une approche suggérée était d'ignorer le TimeSpan pour la sérialisation, et à la place sérialiser le résultat de TimeSpan.Ticks (et utiliser new TimeSpan(ticks) pour la désérialisation). Un exemple de ceci suit:

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

Bien que cela semble fonctionner dans mon test - est-ce la meilleure façon de - ils y parvenir?

Existe-t-il un meilleur moyen de sérialiser un TimeSpan vers et depuis XML?

186
demandé sur John Saunders 2009-03-12 12:55:13

13 réponses

La façon dont vous avez déjà posté est probablement le plus propre. Si vous n'aimez pas la propriété supplémentaire, vous pouvez implémenter IXmlSerializable, mais alors vous devez faire Tout , ce qui bat largement le point. J'utiliserais volontiers l'approche que vous avez publiée; c'est (par exemple) efficace (pas d'analyse complexe, etc.), les numéros de type culture indépendants, non ambigus et timestamp sont facilement et généralement compris.

En aparté, j'ajoute souvent:

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

Cela le cache simplement dans l'interface utilisateur et dans référencement dll pour éviter toute confusion.

64
répondu Marc Gravell 2009-03-12 10:08:54

Ce n'est qu'une légère modification sur l'approche suggérée dans la question, mais ce Microsoft Connect question recommande d'utiliser une propriété pour la sérialisation comme ceci:

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

Cela sérialiserait un TimeSpan de 0: 02: 45 comme:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Alternativement, le DataContractSerializer prend en charge TimeSpan.

88
répondu Rory MacLeod 2013-07-21 15:58:02

Quelque chose qui peut fonctionner dans certains cas est de donner à votre propriété publique un champ de sauvegarde, qui est un TimeSpan, mais la propriété publique est exposée sous forme de chaîne.

Par exemple:

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

Ceci est ok si la valeur de la propriété est utilisée principalement w / dans la classe contenant ou héritant des classes et est chargée à partir de la configuration xml.

Les autres solutions proposées sont meilleures si vous voulez que la propriété publique soit une valeur de TimeSpan utilisable pour d'autres classes.

28
répondu Wes 2011-09-06 19:01:17

Combinant une réponse de sérialisation des couleurs et Cette solution originale (ce qui est génial en soi) j'ai eu cette solution:

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

XmlTimeSpan classe est comme ceci:

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); }
    }
}
18
répondu Mikhail 2017-05-23 12:10:40

Vous pouvez créer un wrapper léger autour de la structure 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());
        }
    }
}

Exemple de résultat sérialisé:

<Entry>
  <StartTime>2010-12-06T08:45:12.5</StartTime>
  <Duration>2.08:29:35.2500000</Duration>
</Entry>
8
répondu phoog 2010-12-08 21:05:38

Une option plus lisible serait de sérialiser en tant que chaîne et d'utiliser la méthode TimeSpan.Parse pour la désérialiser. Vous pouvez faire la même chose que dans votre exemple, mais en utilisant TimeSpan.ToString() dans le getter et TimeSpan.Parse(value) dans le setter.

8
répondu Rune Grimstad 2015-04-28 15:19:30

Une autre option serait de le sérialiser en utilisant la classe SoapFormatter plutôt que la classe XmlSerializer.

Le fichier XML résultant semble un peu différent...certains"savon" -balises préfixées, etc...mais il peut le faire.

Voici ce que SoapFormatter sérialisé un timespan de 20 heures et 28 minutes sérialisé à:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

Pour utiliser la classe SOAPFormatter, vous devez ajouter une référence à System.Runtime.Serialization.Formatters.Soap et utiliser l'espace de noms du même nom.

2
répondu Liam 2015-04-28 15:20:12

Ma version de la solution :)

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

Edit: en supposant qu'il est nullable...

[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
répondu Gildor 2014-02-14 10:54:46

Timespan stocké en xml en nombre de secondes, mais il est facile à adopter, j'espère. Timespan sérialisé manuellement (implémentant 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));
            }
       }
    }
}

Il y a un exemple plus complet: https://bitbucket.org/njkazakov/timespan-serialization

Regardez les paramètres.cs. Et il y a du code délicat à utiliser XmlElementAttribute.

1
répondu askazakov 2015-05-26 08:14:31

Pour la sérialisation des contrats de données, j'utilise ce qui suit.

  • garder la propriété sérialisée privée maintient l'interface publique propre.
  • L'Utilisation du nom de propriété publique pour la sérialisation maintient le XML propre.
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
répondu JRS 2012-04-03 01:10:43

Ne peut pas commenter ou classer, mais le commentaire SoapDuration

[XmlElement, Type=SoapDuration]
public TimeSpan TimeSinceLastEvent

Ou

public SoapDuration TimeSinceLastEventTicks
0
répondu david valentine 2012-07-31 09:02:05

Si vous ne voulez pas de solutions de contournement, utilisez la classe DataContractSerializer de System.Runtime.Sérialisation.DLL.

        using (var fs = new FileStream("file.xml", FileMode.Create))
        {
            var serializer = new DataContractSerializer(typeof(List<SomeType>));
            serializer.WriteObject(fs, _items);
        }
0
répondu Sasha Yakobchuk 2017-09-21 18:13:49

Essayez ceci :

//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); }
        }
-2
répondu Manvendra 2012-03-09 04:17:45