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?
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.
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.
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.
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; }
Où 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); }
}
}
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>
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.
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.
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;
}
}
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.
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
Ne peut pas commenter ou classer, mais le commentaire SoapDuration
[XmlElement, Type=SoapDuration]
public TimeSpan TimeSinceLastEvent
Ou
public SoapDuration TimeSinceLastEventTicks
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);
}
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); }
}