วิธีการทำให้อนุกรม TimeSpan เป็น XML


206

ฉันพยายามทำให้เป็นTimeSpanวัตถุ. NET กับ XML และมันไม่ทำงาน Google ด่วนแนะนำว่าในขณะที่TimeSpanต่อเนื่องกันได้นั้นXmlCustomFormatterจะไม่มีวิธีในการแปลงอTimeSpanอบเจ็กต์เป็นและจาก XML

วิธีหนึ่งที่แนะนำคือการเพิกเฉยต่อการTimeSpanทำให้เป็นอนุกรมและแทนที่จะทำให้ผลลัพธ์ของTimeSpan.Ticks(และใช้new TimeSpan(ticks)สำหรับการดีซีเรียลไลเซชัน) แทน ตัวอย่างของสิ่งต่อไปนี้:

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

ในขณะนี้ดูเหมือนว่าจะทำงานในการทดสอบสั้น ๆ ของฉัน - นี่คือวิธีที่ดีที่สุดเพื่อให้บรรลุนี้

มีวิธีที่ดีกว่าในการอนุกรม TimeSpan ไปและกลับจาก XML หรือไม่


4
คำตอบของ Rory MacLeod ด้านล่างเป็นวิธีที่ Microsoft แนะนำให้ทำเช่นนี้
Jeff

2
ฉันจะไม่ใช้เห็บยาวสำหรับ TimeSpand เพราะประเภทระยะเวลาของ XML คือการจับคู่ที่ตรงกัน ปัญหานี้ได้รับการหยิบยกขึ้นมาสู่ Microsoft ในปี 2551 แต่ไม่เคยแก้ไข มีวิธีการแก้ปัญหาที่บันทึกไว้ในตอนนั้น: kennethxu.blogspot.com/2008/09/…
Kenneth Xu

คำตอบ:


71

วิธีที่คุณโพสต์ไปแล้วอาจเป็นวิธีที่สะอาดที่สุด หากคุณไม่ชอบสถานที่ให้บริการพิเศษคุณสามารถดำเนินการIXmlSerializableได้ แต่คุณต้องทำทุกอย่างซึ่งเอาชนะจุดนั้นเป็นส่วนใหญ่ ฉันใช้วิธีการที่คุณโพสต์อย่างมีความสุข มันเป็น (ตัวอย่าง) ที่มีประสิทธิภาพ (ไม่มีการแยกวิเคราะห์ที่ซับซ้อน ฯลฯ ) วัฒนธรรมที่เป็นอิสระตัวเลขที่ไม่คลุมเครือและการประทับเวลาสามารถเข้าใจได้ง่ายและโดยทั่วไป

นอกจากนี้ฉันมักจะเพิ่ม:

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

นี่เพิ่งซ่อนไว้ใน UI และอ้างอิง dlls เพื่อหลีกเลี่ยงความสับสน


5
การทำทุกอย่างไม่ได้เลวร้ายอะไรถ้าคุณใช้อินเทอร์เฟซบนโครงสร้างที่ล้อม System.TimeSpan แทนที่จะนำไปใช้กับ MyClass จากนั้นคุณจะต้องเปลี่ยนประเภทในคุณสมบัติ MyClass.TimeSinceLastEvent ของคุณ
phoog

103

นี่เป็นเพียงการปรับเปลี่ยนเล็กน้อยในวิธีการที่แนะนำในคำถามนี้แต่ปัญหา Microsoft Connectแนะนำให้ใช้คุณสมบัติสำหรับการทำให้เป็นอนุกรมดังนี้:

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

สิ่งนี้จะทำให้อนุกรมเวลาเป็น 0:02:45 เมื่อ:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

หรือDataContractSerializerสนับสนุน TimeSpan


15
+1 สำหรับ XmlConvert.ToTimeSpan () มันจัดการกับไวยากรณ์ระยะเวลามาตรฐาน ISO สำหรับ timespan เช่น PT2H15M ดูen.wikipedia.org/wiki/ISO_8601#Durations
yzorg

2
แก้ไขฉันถ้าฉันผิด แต่ TimeSpan "PT2M45S" ที่ Serlized คือ 00:02:45 ไม่ใช่ 2:45:00
Tom Pažourek

การเชื่อมโยงการเชื่อมต่อจะเสียตอนนี้บางทีมันอาจจะถูกแทนที่ด้วยนี้: connect.microsoft.com/VisualStudio/feedback/details/684819/... ? เทคนิคนี้มีลักษณะแตกต่างกันเล็กน้อย ...
TJB

มีคำถามแปลก ๆ ที่ต้องติดตามเรามีวิธีการคำนวณค่า PT2M45S ไปยัง Time ใน SQL หรือไม่?
Xander

28

สิ่งที่สามารถทำงานได้ในบางกรณีคือการให้เขตข้อมูลสำรองของคุณเป็น TimeSpan แต่ทรัพย์สินสาธารณะถูกเปิดเผยเป็นสตริง

เช่น:

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

สิ่งนี้ใช้ได้ถ้าค่าคุณสมบัติส่วนใหญ่ใช้กับคลาสที่มีอยู่หรือสืบทอดคลาสและถูกโหลดจากคอนฟิกูเรชัน xml

โซลูชันที่เสนออื่น ๆ จะดีกว่าถ้าคุณต้องการให้ทรัพย์สินสาธารณะเป็นค่า TimeSpan ที่ใช้งานได้สำหรับคลาสอื่น ๆ


โดยวิธีที่ง่ายที่สุด ฉันได้มาในสิ่งเดียวกันและทำงานเหมือนมีเสน่ห์ ใช้งานง่ายและเข้าใจ
wpfwannabe

1
นี่คือทางออกที่ดีที่สุดที่นี่ มันเป็นอนุกรมที่ดีมาก !!! ขอบคุณสำหรับการป้อนข้อมูลเพื่อน!
นักพัฒนา

25

การรวมคำตอบจากการจัดลำดับสีและโซลูชันดั้งเดิมนี้ (ซึ่งยอดเยี่ยมด้วยตัวเอง) ฉันได้โซลูชันนี้:

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

ที่XmlTimeSpanชั้นเป็นเช่นนี้

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

วิธีที่ดีที่สุดและง่ายในการแก้ปัญหานี้ ... สำหรับฉัน
Moraru Viorel

นี่เป็นความคิดสร้างสรรค์อย่างแน่นอน - ฉันประทับใจสุด ๆ !
Jim

9

คุณสามารถสร้าง wrapper แสงรอบโครงสร้าง 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());
        }
    }
}

ตัวอย่างผลลัพธ์แบบอนุกรม:

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

แนวคิดใดที่จะทำให้เอาต์พุตเป็น XmlAttribute
ala

@ala หากฉันเข้าใจคำถามของคุณถูกต้องคำตอบคือใช้ XmlAttributeAttribute กับคุณสมบัติที่คุณต้องการแสดงเป็นแอททริบิวต์ ที่ไม่เฉพาะกับ TimeSpan แน่นอน
phoog

+1 ดียกเว้นฉันจะไม่เรียงลำดับเป็นสตริง แต่จะTicksยาว
ChrisWue

@ChrisWue ในออฟฟิศของฉันเราใช้ xml serialization เมื่อเราต้องการเอาท์พุทที่มนุษย์อ่านได้ การจัดลำดับไทม์แพนเป็นระยะเวลานานอาจไม่เข้ากันกับเป้าหมายนั้น หากคุณใช้การทำให้เป็นอันดับ xml ด้วยเหตุผลที่แตกต่างกันแน่นอนว่าการทำอนุกรมของเห็บอาจทำให้เข้าใจได้ง่ายขึ้น
phoog

8

ตัวเลือกที่อ่านได้มากขึ้นคือการทำให้ซีเรียลTimeSpan.Parseไลซ์เป็นสตริงและใช้วิธีการในการดีซีเรียลไลซ์ คุณสามารถทำเช่นเดียวกับในตัวอย่างของคุณ แต่ใช้TimeSpan.ToString()ใน getter และTimeSpan.Parse(value)ใน setter


2

ตัวเลือกอื่นจะทำให้เป็นอันดับโดยใช้SoapFormatterคลาสแทนXmlSerializerคลาส

ไฟล์ XML ที่ได้นั้นมีลักษณะที่แตกต่างออกไปเล็กน้อย ... บางแท็ก "SOAP" ที่ผ่านการแก้ไขแล้ว ฯลฯ ... แต่สามารถทำได้

นี่คือสิ่งที่SoapFormatterทำให้ไทม์แพนของ 20 ชั่วโมงและ 28 นาทีต่อเนื่องกันกับ:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

ในการใช้คลาส SOAPFormatter จำเป็นต้องเพิ่มการอ้างอิงSystem.Runtime.Serialization.Formatters.Soapและใช้เนมสเปซที่มีชื่อเดียวกัน


นี่คือวิธีที่มันทำให้เป็นอันดับใน. net 4.0
Kirk Broadhurst

1

วิธีแก้ปัญหาเวอร์ชันของฉัน :)

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

แก้ไข: สมมติว่ามันเป็นโมฆะ ...

[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

ไทม์แปนที่เก็บไว้ใน xml เป็นจำนวนวินาที แต่ฉันหวังว่าจะนำไปใช้ง่าย Timespan ต่อเนื่องด้วยตนเอง (การใช้ 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));
            }
       }
    }
}

มีตัวอย่างที่ครอบคลุมมากขึ้น: https://bitbucket.org/njkazakov/timespan-serialization

ดูที่ Settings.cs และมีรหัสที่ยุ่งยากในการใช้ XmlElementAttribute


1
กรุณาอ้างอิงข้อมูลที่เกี่ยวข้องจากลิงค์นั้น ข้อมูลทั้งหมดที่จำเป็นสำหรับคำตอบของคุณควรอยู่ในเว็บไซต์นี้จากนั้นคุณสามารถอ้างอิงลิงค์นั้นเป็นแหล่งที่มา
SuperBiasedMan

0

สำหรับสัญญา data data serialization ฉันใช้ดังต่อไปนี้

  • การทำให้คุณสมบัติที่ต่อเนื่องเป็นส่วนตัวทำให้อินเทอร์เฟซสาธารณะสะอาด
  • การใช้ชื่อคุณสมบัติสาธารณะสำหรับการทำให้เป็นอนุกรมจะทำให้ XML สะอาด
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

ถ้าคุณไม่ต้องการแก้ไขปัญหาใด ๆ ใช้คลาส DataContractSerializer จาก 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

ลองสิ่งนี้:

//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); }
        }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.