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?

joeldixon66
źródło
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ń.

Marc Gravell
źródło
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.

Rory MacLeod
źródło
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.

Wes
źródło
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); }
    }
}
Michaił
źródło
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>
phoog
źródło
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.

Rune Grimstad
źródło
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.

Liam
źródło
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;
    }
}
Gildor
źródło
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.

askazakov
źródło
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
JRS
źródło
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);
        }
Sasha Yakobchuk
źródło
-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); }
        }
Manvendra
źródło