C#——XML序列化

开发环境

VS2022

.net core 6.0

序列化概念

序列化是将内存中的对象或者对象图(一组相互引用的对象)拉平为一个可以保存或进行传输的字节流,或者XML节点。反序列化正好相反,它把数据流重新构造成内存中的一个对象或者对象图。

序列化用途

序列化和反序列化通常用于:

· 通过网络或程序边界传输对象

· 在文件或者数据库中保存对象

此外,它还可以用于深度克隆对象。而数据契约和XML序列化引擎也可以当作通用工具,用于加载和保存已知结构的XML文件。

可序列化的项

  • 公共类的公共读/写属性和字段。

  • 执行 ICollection 或 IEnumerable 的类 。

  • XmlElement 对象。

  • XmlNode 对象。

  • DataSet 对象。

序列化常用特性

XmlRoot------申明根结点,如: [XmlRoot(ElementName = "Root")]

XmlIgnore------忽悠某个属性或字段,如 :[XmlIgnore] public int Id;

XmlInclude------是否包含某个类,如:[XmlInclude(typeof(Results))]

XmlArray------申明集合

XmlArrayItem------申明集合中元素,如下:

cs 复制代码
        [XmlArray("Targets", Namespace = "TwoPoint")]
        [XmlArrayItem("Target", Namespace = "TwoPoint")]
        public Point[] Points { get; set; }

XML 序列化注意事项

使用 XmlSerializer 类时,应注意以下事项:

  • 序列化数据只包含数据本身和类的结构。

  • 只能序列化公共属性和字段。 属性必须具有公共访问器(get 和 set 方法)。

  • 类必须具有无参数构造函数才能被 XmlSerializer 序列化。

  • 方法不能被序列化。

  • 如下所述,如果实现 IEnumerable 或 ICollection 的类满足某些要求,XmlSerializer 则可以处理这些类 。

    实现 IEnumerable 的类必须实现采用单个参数的公共 Add 方法 。

    实现 ICollection 的类(如 CollectionBase)必须具有采用整型的公共"Item"索引属性(在 C# 中为索引器),而且它必须有一个"integer"类型的公共"Count"属性 。 传递给 Add 方法的参数必须与从"Item"属性返回的类型相同,或者为此类型的基之一 。

常见错误及解决办法

以下为愚初次使用时遇到的一引问题,及解决办法:

不同命名空间下的相同类名下异常

InvalidOperationException: Types 'WpfApp.Xml.Results.Target' and 'WpfApp.Xml.PlanInfo.Target' both use the XML type name, 'Target', from namespace ''. Use XML attributes to specify a unique XML name and/or namespace for the type.

解决办法:如上黄色字体,即为序列化的对象添加Namespace,如下

cs 复制代码
    [XmlType(Namespace = "WpfApp.Xml.Results")]
    public class Target
    {
        /// <summary>
        /// 编号
        /// </summary>
        [XmlAttributeAttribute(AttributeName = "id")]
        public double NO { get; set; }

    }

注意仅需要添加Namespace即可,不需要添加TypeName。

很多文章提到应该添加TypeName和Namespace,如下的操作方式:

cs 复制代码
    [XmlType(TypeName = "PTarget", Namespace = "WpfApp.Xml.Info")]
    public class Target
    {
        /// <summary>
        /// 编号
        /// </summary>
        [XmlAttributeAttribute(AttributeName = "id")]
        public double NO { get; set; }

    }

但若添加了TypeName会导致你后一个不同NameSpace下的类改为你Namespace中设置的名称。

非集合属性或字段添加了集合特性

InvalidOperationException: For non-array types, you may use the following attributes: XmlAttribute, XmlText, XmlElement, or XmlAnyElement.

集合属性或字段添加了 XmlElement

集合不能使用它,否则会导致它的子元素全部显示为Targets,而应该如下:

[XmlArray("Targets")]

[XmlArrayItem("Target")]

public Point[] Points { get; set; }

XmlArray指定了集合Points在Xml中的显示名称,

XmlArrayItem指定了集合中元素在Xml中的显示名称。

对象序列化与反序列化常用代码

对象序列化为XML文件

cs 复制代码
        /// <summary>
        /// 序列化对象为XML文件
        /// </summary>
        internal static void XmlSerializer<T>(string fileName, T t) where T : new()
        {

            try
            {

                XmlSerializer serializer = new(typeof(SerializationWrapper));
                using TextWriter tr = new StreamWriter(fileName, false, Encoding.UTF8);
                // 序列化包装类
                serializer.Serialize(tr, t);
                tr.Close();
                tr.Dispose();
            }
            catch (Exception ex)
            {
                Debug.WriteLine("序列化失败");
                Exception exception = new("序列化失败");
                throw exception;
            }
        }

xml文件反序化为对象

cs 复制代码
        /// <summary>
        /// 反序列化XML文件为对象
        /// </summary>
        /// <param name="fileName">文件名</param
        internal static T XmlDeserialize<T>(string fileName) where T : new()  
        {
            using FileStream s = File.OpenRead(fileName);

            // 创建包装类的实例并添加对象
            XmlSerializer xs = new(typeof(T));
            T sw = (T)xs.Deserialize(s);
            return sw;
        }

上述代码最好还是添加上try......catch......以进行错误处理。

对象序列化为字符串

cs 复制代码
        /// <summary>
        /// 将对象序列化为XML字符串
        /// </summary>
        /// <typeparam name="T">需要序列化的类型</typeparam>
        /// <param name="t">序列化的对象实例</param>
        /// <returns>序列化对象的字符串</returns>
        internal static string Object2XmlStringSerializer<T>(T t) where T : new()
        {
            try
            {
                // 创建包装类的实例并添加对象
                T wrapper = t;
                // 创建XmlSerializer的实例,指定根元素名称
                XmlSerializer serializer = new(typeof(T));

                // 使用StringWriter来捕获序列化后的XML内容
                using (StringWriter stringWriter = new())
                {
                    serializer.Serialize(stringWriter, wrapper);
                    string xmlContent = stringWriter.ToString();
                    Debug.WriteLine(xmlContent);
                    return xmlContent;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("序列化失败");
                Exception exception = new("序列化失败");
                throw exception;
            }
        }

若需要指定编码方式,那么就需要转化为数据流,然后指定相应的编码方式后再读取,如下:

cs 复制代码
                using (MemoryStream memoryStream = new ())
                {
                    using (StreamWriter streamWriter = new (memoryStream, Encoding.UTF8))
                    {
                        serializer.Serialize(streamWriter, t);
                        streamWriter.Flush();
                        memoryStream.Position = 0;
                        using (StreamReader streamReader = new (memoryStream, Encoding.UTF8))
                        {
                            string xmlContent = streamReader.ReadToEnd();
                            Debug.WriteLine(xmlContent);
                            return xmlContent;
                        }
                    }
                }

其实没有必要指定编码方式,从测试结果来看,反序列化时并不需要指定相应的编码方式,也可以正确进行解码。

xml字符串反序化为对象

cs 复制代码
/// <summary>
/// 将XML字符串反序列化为对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="xmlString"></param>
/// <returns></returns>
internal static T XmlStringDeserializer<T>(string xmlString) where T : new()
{
    // 使用StringReader包装XML字符串
    using StringReader stringReader = new(xmlString);
    try
    {
        XmlSerializer serializer = new(typeof(T));
        // 反序列化XML字符串
        T root = (T)serializer.Deserialize(stringReader);
        return root;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

参考资料

《C# 核心技术指南(原书第7版)》

XML 序列化详细信息 - .NET | Microsoft Learn

相关推荐
君莫愁。19 分钟前
【Unity】检测鼠标点击位置是否有2D对象
unity·c#·游戏引擎
Lingbug1 小时前
.Net日志组件之NLog的使用和配置
后端·c#·.net·.netcore
咩咩觉主1 小时前
Unity实战案例全解析:PVZ 植物卡片状态分析
unity·c#·游戏引擎
Echo_Lee01 小时前
C#与Python脚本使用共享内存通信
开发语言·python·c#
__water8 小时前
『功能项目』QFrameWork框架重构OnGUI【63】
c#·unity引擎·重构背包框架
Crazy Struggle9 小时前
C# + WPF 音频播放器 界面优雅,体验良好
c#·wpf·音频播放器·本地播放器
晨曦_子画9 小时前
C#实现指南:将文件夹与exe合并为一个exe
c#
花开莫与流年错_10 小时前
C# 继承父类,base指定构造函数
开发语言·c#·继承·base·构造函数
hillstream310 小时前
oracle中NUMBER(1,0)的字段如何映射到c#中
数据库·oracle·c#
那个那个鱼10 小时前
.NET 框架版本年表
开发语言·c#·.net