C# 如何将复杂的嵌套对象序列化为XML?多种实现方案详解

来源:CDN教程作者:上海网站建设头衔:草根站长
导读:本期聚焦于上海网站建设创作的《C# 如何将复杂的嵌套对象序列化为XML?多种实现方案详解》,敬请观看详情。将复杂的嵌套对象序列化为XML是C#开发中常见的需求,比如生成配置文件、与第三方系统交换数据、生成报表等场景都离不开它。本文围绕XmlSerializer这一核心工具,详细讲解如何定义类结构、如何处理集合、字典以及嵌套属性等复杂情况,并通过完整代码演示序列化与反序列化的全过程。同时介绍了使用特性控制节点名称、忽略字段、处理命名空间等实用技巧,还对比了XmlSerializer与DataContractSerializer的差异,帮助你根据实际业务场景选择最合适的序列化方案,避开常见的坑。

在C#开发中,XML仍然是一种重要的数据交换格式,许多配置文件、Web服务接口、第三方系统对接都要求以XML形式传递数据。当对象结构简单时序列化并不困难,但一旦遇到多层嵌套、集合、字典等复杂结构,很多开发者就会遇到各种奇怪的问题,比如某些属性莫名消失、生成的XML结构不符合预期等。本文将系统地讲解如何在C#中把复杂的嵌套对象正确地序列化为XML。

C# 如何将复杂的嵌套对象序列化为XML?多种实现方案详解

一、使用XmlSerializer序列化嵌套对象的基本方法

XmlSerializer是.NET中最常用的XML序列化工具,位于System.Xml.Serialization命名空间下。它的工作原理是基于对象的公共属性和公共字段生成XML,不需要额外标注特性也能工作。对于嵌套对象,XmlSerializer会自动递归处理,将内层对象的属性展开为子节点。

下面通过一个典型的三层嵌套结构来演示。假设我们需要描述一个订单系统,订单包含客户信息、多个商品条目,每个商品又有自己的分类信息,这种多层结构在业务系统中非常常见。

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class OrderItem
{
    public string ProductName { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
    public Category Category { get; set; }  // 嵌套对象
}

public class Customer
{
    public string Name { get; set; }
    public string Phone { get; set; }
}

public class Order
{
    public string OrderNo { get; set; }
    public DateTime CreateTime { get; set; }
    public Customer Customer { get; set; }              // 嵌套对象
    public List<OrderItem> Items { get; set; }       // 嵌套集合
}

class Program
{
    static void Main()
    {
        var order = new Order
        {
            OrderNo = "SO-2024-001",
            CreateTime = DateTime.Now,
            Customer = new Customer { Name = "张三", Phone = "13800138000" },
            Items = new List<OrderItem>
            {
                new OrderItem
                {
                    ProductName = "机械键盘", Price = 399.00m, Quantity = 1,
                    Category = new Category { Id = 5, Name = "电脑外设" }
                },
                new OrderItem
                {
                    ProductName = "无线鼠标", Price = 129.50m, Quantity = 2,
                    Category = new Category { Id = 5, Name = "电脑外设" }
                }
            }
        };

        var serializer = new XmlSerializer(typeof(Order));
        var ns = new XmlSerializerNamespaces();
        ns.Add("", "");  // 去掉默认命名空间,输出更干净

        using (var stream = new MemoryStream())
        {
            serializer.Serialize(stream, order, ns);
            string xml = System.Text.Encoding.UTF8.GetString(stream.ToArray());
            Console.WriteLine(xml);
        }
    }
}

执行后可以看到,Order的属性成为根节点的直接子节点,Customer对象被展开为一个子节点,其内部属性继续作为孙节点,List集合中的每个OrderItem都会生成一个同名子节点,嵌套的Category同样被递归展开。整个层级结构清晰,完全自动化,不需要手动干预。

需要注意的是,XmlSerializer有几个隐含规则:只序列化公共的读写属性和公共字段;只处理无参构造函数能创建的类型;被static、readonly修饰的字段会被忽略。如果某个属性没有被序列化,首先应该检查是否违反了这些规则。

二、用特性精确控制XML结构

默认生成的XML节点名与类名、属性名一致,但实际对接第三方系统时,往往要求特定的节点名称或属性格式。这时就需要借助System.Xml.Serialization命名空间下的各种特性来精细化控制。

常用的控制特性包括:XmlRoot控制根节点名称,XmlElement控制子节点名称,XmlAttribute把属性输出为XML特性而非子节点,XmlIgnore完全忽略某个属性,XmlArray和XmlArrayItem控制集合节点的外层包装。通过组合这些特性,几乎可以生成任意想要的XML结构。

[XmlRoot("order")]
public class Order
{
    [XmlAttribute("no")]
    public string OrderNo { get; set; }

    [XmlElement("created")]
    public DateTime CreateTime { get; set; }

    [XmlIgnore]
    public string InternalRemark { get; set; }  // 不参与序列化

    [XmlElement("customer")]
    public Customer Customer { get; set; }

    [XmlArray("items")]
    [XmlArrayItem("item")]
    public List<OrderItem> Items { get; set; }
}

上面的配置会让订单号以特性形式输出在根节点上,创建时间节点改名为created,集合外层多一层items包装,每个元素叫item。如果希望集合没有外层包装、元素直接平铺在父节点下,把XmlArray和XmlArrayItem换成XmlElement即可,这是处理列表时一个非常实用的小技巧。

还有一种进阶用法:当同一个属性可能存放不同类型的对象时,可以使用XmlInclude配合继承体系实现多态序列化。例如定义一个基类Payment和子类AliPay、WechatPay,在序列化时XmlSerializer会自动在节点上附加xsi:type特性来记录具体类型,反序列化时能正确还原。使用多态时必须在XmlSerializer的构造函数或XmlInclude中提前声明所有可能的子类型,否则运行时会抛出异常。

三、处理字典和特殊类型的难点

XmlSerializer有一个广为人知的限制:不支持Dictionary类型。这是因为它无法在XML中表示键值对的语义。解决办法是自定义一个可序列化的键值对结构,把字典转换后再序列化。

public class SerializableKeyValue
{
    public string Key { get; set; }
    public string Value { get; set; }
}

public class Config
{
    [XmlArray("settings")]
    [XmlArrayItem("setting")]
    public List<SerializableKeyValue> Settings { get; set; }
}

// 序列化前转换
var config = new Config
{
    Settings = dict.Select(kv => new SerializableKeyValue
    {
        Key = kv.Key,
        Value = kv.Value
    }).ToList()
};

除了字典,还有一些类型需要特别留意。枚举默认输出名称字符串,可以通过XmlEnum特性自定义;Nullable<int>这类可空类型,值为null时对应节点会整个消失,而不是输出空节点;DateTime默认输出ISO 8601格式,如果对接方要求yyyy-MM-dd这样的自定义格式,最简单的办法是在实体类中增加一个string类型的辅助属性,用XmlElement标注真实输出属性,把原属性标记为XmlIgnore。

此外,如果对象中存在循环引用,比如父对象引用子对象、子对象又反过来引用父对象,XmlSerializer在序列化时会直接抛出异常。这种结构在ORM实体中很常见,需要在序列化前先把数据映射到专门的DTO对象上,断开循环引用,这也是推荐的工程实践——让传输模型与业务模型分离。

四、反序列化与方案选型建议

序列化的逆过程同样简单,调用Deserialize方法即可把XML还原为对象。对于嵌套结构,XmlSerializer会按照类型定义自动重建整棵对象树,集合、嵌套对象都能正确恢复。要注意反序列化时构造的XmlSerializer实例的typeof类型必须与XML根节点对应,否则会报错。

using (var reader = new StringReader(xmlContent))
{
    var serializer = new XmlSerializer(typeof(Order));
    var order = (Order)serializer.Deserialize(reader);
    Console.WriteLine(order.Customer.Name);
}

关于性能有一个容易被忽视的要点:XmlSerializer在首次构造时会动态生成序列化程序集,这个过程比较耗时。在循环中反复new XmlSerializer会造成明显的性能浪费,正确做法是缓存静态实例复用。对于同一个类型,XmlSerializer内部有缓存机制,只要传入的参数一致,重复构造的开销其实可控,但在高频调用场景下仍然建议显式缓存。

如果项目对互操作性要求更高,还可以考虑DataContractSerializer,它基于DataContract和DataMember特性工作,支持字典类型,通过KnownType处理多态,生成的XML结构可以通过Order参数控制是按成员顺序还是按字母排列。总的来说,简单直观、需要精细控制节点结构时选XmlSerializer;需要支持字典、关注性能和数据契约时选DataContractSerializer。掌握这些技巧后,再复杂的嵌套对象都能轻松转换成符合要求的XML。

C# XML序列化嵌套对象序列化XmlSerializer修改时间:2026-09-01 13:30:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。