Question

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 des objets TimeSpan vers et à partir de XML.

Une approche suggérée consistait à ignorer TimeSpan pour la sérialisation et à sérialiser le résultat de TimeSpan.Ticks (et à utiliser un nouveau TimeSpan (ticks) pour la désérialisation). Voici un exemple:

[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 mes brefs essais - est-ce le meilleur moyen d'y parvenir?

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

Était-ce utile?

La solution

La façon dont vous avez déjà posté est probablement la plus propre. Si vous n'aimez pas la propriété extra, vous pouvez implémenter IXmlSerializable , mais vous devez alors faire tout , ce qui fait largement échouer le point. J'utiliserais avec plaisir l'approche que vous avez publiée; il est (par exemple) efficace (pas d'analyse complexe, etc.), indépendant de la culture, sans ambiguïté et les numéros de type timestamp sont facilement et couramment compris.

En passant, j'ajoute souvent:

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

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

Autres conseils

Il ne s'agit que d'une légère modification de l'approche proposée dans la question mais ce problème Microsoft Connect recommande d'utiliser une propriété pour la sérialisation de ce type:

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

Ceci sérialiserait un TimeSpan de 0:02:45 en tant que:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Sinon, DataContractSerializer prend en charge TimeSpan.

Dans certains cas, il peut être utile de donner à votre propriété publique un champ de sauvegarde, qui est un TimeSpan, mais la propriété publique est exposée en tant que chaîne.

par exemple:

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

Ceci est acceptable si la valeur de la propriété est principalement utilisée dans la classe contenante ou dans les classes héritées et est chargée à partir de la configuration xml.

Les autres solutions proposées sont préférables si vous souhaitez que la propriété publique soit une valeur TimeSpan utilisable pour d'autres classes.

Combinaison d'une réponse de Sérialisation des couleurs et cette solution originale (ce qui est formidable en soi) J'ai cette solution:

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

XmlTimeSpan 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 consisterait à sérialiser sous forme de chaîne et à 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 (valeur) 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 a un aspect un peu différent ... certaines balises préfixées "SOAP", etc ... mais il peut le faire.

Voici ce que SoapFormatter a sérialisé en 20 heures et 28 minutes en série:

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

Éditer: en supposant que ce soit nul ...

[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 est 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 existe un exemple plus complet: https://bitbucket.org/njkazakov/timespan-serialization

Regardez Paramètres.cs. Et il existe un code difficile à utiliser pour XmlElementAttribute.

Pour la sérialisation du contrat de données, j'utilise les éléments suivants.

  • Garder la propriété sérialisée privée maintient l'interface publique propre.
  • L'utilisation du nom de la propriété publique pour la sérialisation conserve le code 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

Si vous ne souhaitez aucune solution de contournement, utilisez la classe DataContractSerializer de System.Runtime.Serialization.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); }
        }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top