关于c#:如何将TimeSpan序列化为XML

How to serialize a TimeSpan to XML

我正试图将.NET TimeSpan对象序列化为XML,但它不起作用。一个快速的Google提出,虽然TimeSpan是可序列化的,但是XmlCustomFormatter不提供将TimeSpan对象转换为XML和从XML转换为XML的方法。

一种建议的方法是忽略用于序列化的TimeSpan,而将TimeSpan.Ticks的结果序列化(并使用new TimeSpan(ticks)进行反序列化)。下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[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); }
    }
}

虽然这在我的简短测试中似乎有效-这是实现这一目标的最佳方法吗?

是否有更好的方法来序列化XML之间的时间跨度?


这只是对问题中建议的方法的一个细微修改,但此Microsoft Connect问题建议使用如下的属性进行序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[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的时间跨度序列化为:

1
<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

或者,DataContractSerializer支持时间跨度。


你已经发布的方式可能是最干净的。如果你不喜欢这个额外的属性,你可以实现IXmlSerializable,但是你必须做所有的事情,这在很大程度上是失败的。我很乐意使用您发布的方法;它(例如)高效(不需要复杂的解析等)、独立于文化、不含糊和时间戳类型的数字易于理解。

作为旁白,我经常补充:

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

这只是将其隐藏在UI和引用DLL中,以避免混淆。


在某些情况下可以工作的是为公共属性提供一个支持字段,它是一个时间跨度,但是公共属性作为字符串公开。

如:

1
2
3
4
5
6
protected TimeSpan myTimeout;
public string MyTimeout
{
    get { return myTimeout.ToString(); }
    set { myTimeout = TimeSpan.Parse(value); }
}

如果属性值主要在包含类或继承类中使用,并且从XML配置加载,则这是正常的。

如果希望公共属性是其他类的可用时间跨度值,则其他建议的解决方案更好。


结合颜色序列化的答案和这个原始解决方案(本身很好),我得到了这个解决方案:

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

其中XmlTimeSpan类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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); }
    }
}


您可以围绕TimeSpan结构创建一个light wrapper:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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());
        }
    }
}

示例序列化结果:

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


一个更可读的选项是作为字符串进行序列化,并使用TimeSpan.Parse方法对其进行反序列化。在getter中使用TimeSpan.ToString(),在setter中使用TimeSpan.Parse(value),与在示例中一样。


另一种选择是使用SoapFormatter类而不是XmlSerializer类序列化它。

生成的XML文件看起来有点不同……一些"soap"前缀标记等……但它可以做到。

以下是SoapFormatter序列化的20小时28分钟的时间跨度:

1
<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

要使用SoapFormatter类,需要添加对System.Runtime.Serialization.Formatters.Soap的引用,并使用相同名称的命名空间。


时间跨度以秒为单位存储在XML中,但我希望它易于采用。手动序列化的TimeSpan(实现IXML可序列化):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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://bitback.org/njkazakov/timespan-serialization

看看settings.cs。还有一些使用xmlementattribute的复杂代码。


我的解决方案版本:)

1
2
3
4
5
6
7
8
[DataMember, XmlIgnore]
public TimeSpan MyTimeoutValue { get; set; }
[DataMember]
public string MyTimeout
{
    get { return MyTimeoutValue.ToString(); }
    set { MyTimeoutValue = TimeSpan.Parse(value); }
}

编辑:假设它可以为空…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[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;
    }
}


如果不需要任何解决方法,请使用System.Runtime.Serialization.dll中的DataContractSerializer类。

1
2
3
4
5
        using (var fs = new FileStream("file.xml", FileMode.Create))
        {
            var serializer = new DataContractSerializer(typeof(List<SomeType>));
            serializer.WriteObject(fs, _items);
        }


对于数据协定序列化,我使用以下内容。

  • 保持序列化属性为私有将保持公共接口的干净。
  • 使用公共属性名进行序列化可以保持XML的整洁。
1
2
3
4
5
6
7
8
9
10
11
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


试试这个:

1
2
3
4
5
6
7
8
9
//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); }
        }