如何将 TimeSpan 序列化为 XML

Posted

技术标签:

【中文标题】如何将 TimeSpan 序列化为 XML【英文标题】:How to serialize a TimeSpan to XML 【发布时间】:2010-10-12 21:11:06 【问题描述】:

我正在尝试将 .NET TimeSpan 对象序列化为 XML,但它不工作。一个快速的谷歌建议虽然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 和从 XML 序列化?

【问题讨论】:

Rory MacLeod 下面的回答实际上是微软建议这样做的方式。 我不会对 TimeSpand 使用长刻度,因为 XML 的持续时间类型是完全匹配的。该问题于 2008 年向 Microsoft 提出,但从未得到解决。当时记录了一个解决方法:kennethxu.blogspot.com/2008/09/… 【参考方案1】:

这只是对问题中建议的方法的轻微修改,但this Microsoft Connect issue 建议使用这样的序列化属性:

[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 的 TimeSpan 序列化为:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

或者,DataContractSerializer 支持 TimeSpan。

【讨论】:

+1 表示 XmlConvert.ToTimeSpan()。它处理时间跨度的 ISO 标准持续时间语法,如 PT2H15M,请参阅en.wikipedia.org/wiki/ISO_8601#Durations 如果我错了,请纠正我,但序列化的 TimeSpan "PT2M45S" 是 00:02:45,而不是 2:45:00。 连接链接现在断开了,也许可以用这个替换:connect.microsoft.com/VisualStudio/feedback/details/684819/…?该技术看起来也有点不同......【参考方案2】:

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

顺便说一句,我经常补充:

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

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

【讨论】:

Doing everything 如果你在一个包装 System.TimeSpan 的结构上实现接口,而不是在 MyClass 上实现它,那么做 everything 并不是那么糟糕。然后您只需更改 MyClass.TimeSinceLastEvent 属性的类型【参考方案3】:

在某些情况下可行的方法是为您的公共属性提供一个支持字段,即 TimeSpan,但公共属性以字符串形式公开。

例如:

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

如果属性值主要在包含类或继承类中使用并从 xml 配置加载,则可以。

如果您希望公共属性成为其他类的可用 TimeSpan 值,则其他建议的解决方案会更好。

【讨论】:

迄今为止最简单的解决方案。我想出了完全相同的东西,它就像一个魅力。易于实施和理解。 这是最好的解决方案。连载非常好!!!谢谢你输入的朋友!【参考方案4】:

结合Color serialization 和this original solution 的答案(这本身就很棒)我得到了这个解决方案:

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

【讨论】:

解决这个问题的最好和最简单的方法......对我来说 这绝对是巧妙的——我印象非常深刻! 哇,超级简单,完美运行!这应该上升到顶部。也许一种可能的方法是使用 Rory MacLeod 的答案中的 XmlConvert.ToTimeSpan() / ToString() 以获得更好的 XML 可读性。【参考方案5】:

您可以在 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,如果我正确理解您的问题,答案是将 XmlAttributeAttribute 应用于您要作为属性表示的属性。当然,这并不是 TimeSpan 所特有的。 +1 很好,只是我不会将其序列化为字符串,而是将Ticks 序列化为 long。 @ChrisWue 在我的办公室里,当我们想要人类可读的输出时,我们会使用 xml 序列化;将时间跨度序列化为 long 与该目标不太兼容。如果您出于其他原因使用 xml 序列化,当然,序列化刻度可能更有意义。【参考方案6】:

更易读的选项是序列化为字符串并使用TimeSpan.Parse 方法对其进行反序列化。您可以执行与示例相同的操作,但在 getter 中使用 TimeSpan.ToString(),在 setter 中使用 TimeSpan.Parse(value)

【讨论】:

【参考方案7】:

我的解决方案版本:)

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

【讨论】:

【参考方案8】:

另一种选择是使用SoapFormatter 类而不是XmlSerializer 类对其进行序列化。

生成的 XML 文件看起来有点不同...一些“SOAP”前缀标签等...但它可以做到。

这是SoapFormatter 序列化的时间跨度为 20 小时 28 分钟的内容:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

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

【讨论】:

这是在 .net 4.0 中序列化的方式【参考方案9】:

时间跨度以秒数存储在 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 的棘手代码。

【讨论】:

请引用该链接中的相关信息。您的答案所需的所有信息都应在此站点上,然后您可以引用该链接作为来源。【参考方案10】:

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

        using (var fs = new FileStream("file.xml", FileMode.Create))
        
            var serializer = new DataContractSerializer(typeof(List<SomeType>));
            serializer.WriteObject(fs, _items);
        

【讨论】:

请注意,DataContractSerializer 不支持 XML 属性之类的东西,但如果您不需要 XML 属性支持,则答案的方法可能很有用。【参考方案11】:

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

将序列化属性保持私有可保持公共接口整洁。 使用公共属性名称进行序列化可保持 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

【讨论】:

【参考方案12】:

试试这个:

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

【讨论】:

以上是关于如何将 TimeSpan 序列化为 XML的主要内容,如果未能解决你的问题,请参考以下文章

如何从 XML 的特定部分将 XML 序列化为对象?

如何将 XML 反序列化为 C# 中的对象? [复制]

如何将简单的类序列化/反序列化为 XML 并返回

如何将不同名称的 XML 节点反序列化为相同的基本类型

如何使用 Json.NET 将 XML 序列化为 JSON 对象

如何将 XML Choice 反序列化为正确的复杂类型