C# 序列化时“检测到循环引用”错误的彻底解决方案

news2024/9/21 18:40:22

目录

一,问题表现 

二、没有技术含量的解决方案 

三、本人彻底的解决方案

简要说明

贴代码

思路解析

思路


一,问题表现 

示例代码如下:

[Serializable]
public class NodeTest 
{
    public NodeTest ()
    {
        new List<NodeTest> ();
    }
    public string Name { get; set; }

    public NodeTest Parent { get; set; }

    public List<NodeTest> Children { get; set; }

}

先看错误地方,以上这个类要是序列化就会遇到,"序列化类型 Test.NodeTest 的对象时检测到循环引用。"错误。

二、没有技术含量的解决方案 

网上一搜,几乎到处都是这两种解决方案:

  1. 使用NewtonSoft.Json,然后使用序列方法,加上设置 ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
  2. 直接在循环错误属性上加XmlIgnore特性。
                NodeTest nd = new NodeTest ();
                nd.Name = "root";
                NodeTest nd1 = new NodeTest ();
                nd1.Name = "child1";
                nd1.Parent = nd;
                NodeTest nd2 = new NodeTest ();
                nd2.Name = "child2";
                nd2.Parent = nd;
                nd.Children.Add ( nd1 );
                nd.Children.Add ( nd2 );

上面的实例,采用第一种方法序列化是这结果:

 采用第二种是以下结果。

由此可见这两种方法简单,粗暴,没有一点技术含量。这么说是因为,直接忽略了其父子关系。反序列化成对象后Parent属性为空,如果需要逆向查找父对象时,完全行不通。

三、本人彻底的解决方案

  • 简要说明

首先将例中NodeTest对象进行改装,让它继承自 IXmlSerializable 接口并实现,为的就是在序列化和反序列化时,可以自由控制以达到序列化时能包含父节点信息。其次是对他的属性Children进行改造,这很重要,如果继续使用List列表,会出现其他问题,这个后续会提到。

  • 贴代码

现不废话,贴代码,代码看完,看后文解析,应该很容易明白,NodeTest 类:

    [Serializable]
    public class NodeTest : IXmlSerializable
    {
        internal const string ROOT = "NodeTest";
        internal const string NAME = "Name";
        internal const string PARENT = "Parent";
        internal const string ELEMENT_EXISTMARK = "ExistMark";
        internal const string ELEMENT_EXIST = "Exist";
        internal const string ELEMENT_NOTEXIST = "NotExist";

        public NodeTest ()
        {
            Children = new NodeTestCollection ();
        }
        public string Name { get; set; }

        public NodeTest Parent { get; set; }

        [XmlArray ()]
        public NodeTestCollection/*List<NodeTest>*/ Children { get; set; }

        public System.Xml.Schema.XmlSchema GetSchema ()
        {
            return null;
        }

        public static void ResetSerializationStatus ()
        {
            _dicAllNodes.Clear ();
        }

        private static Dictionary<string, NodeTest> _dicAllNodes= new Dictionary<string, NodeTest> ();


        public void ReadXml ( System.Xml.XmlReader reader )
        {
            XmlSerializer xmlSer = XmlSerializer.FromTypes ( new Type[ ] { typeof ( NodeTest ) } )[ 0 ];

            if ( reader.IsEmptyElement ) return;

            while ( reader.NodeType != System.Xml.XmlNodeType.EndElement )
            {
                reader.ReadStartElement ( ROOT );
                reader.ReadStartElement ( NAME );
                this.Name = reader.ReadString ();
                reader.ReadEndElement ();
                reader.MoveToContent ();
                string sExistMark = ELEMENT_NOTEXIST;
                if ( reader.MoveToAttribute ( ELEMENT_EXISTMARK ) )
                {
                    sExistMark = reader.GetAttribute ( ELEMENT_EXISTMARK );
                }
                reader.ReadStartElement ( PARENT );
                switch ( sExistMark )
                {
                    case ELEMENT_EXIST:
                        reader.ReadStartElement ( NAME );
                        Parent = new NodeTest ();
                        Parent.Name = reader.ReadString ();
                        reader.ReadEndElement ();
                        reader.ReadEndElement ();
                        break;
                    default:
                        break;
                }

                XmlSerializer xmlSer2 = XmlSerializer.FromTypes ( new Type[ ] { typeof ( NodeTestCollection ) } )[ 0 ];
                Children = ( NodeTestCollection )xmlSer2.Deserialize ( reader );
                if ( Children.Count != 0 )
                {
                    reader.ReadEndElement ();
                }

                _dicAllNodes.Add ( this.Name, this );
            }

            for ( int i = 0 ; i < Children.Count ; i++ )
            {
                var child = Children[ i ];
                if ( child.Parent != null )
                {
                    child.Parent = _dicAllNodes[ child.Parent.Name ];
                }
            }
        }

        public void WriteXml ( System.Xml.XmlWriter writer )
        {
            XmlSerializer xmlSer = XmlSerializer.FromTypes ( new Type[ ] { typeof ( NodeTest ) } )[ 0 ];

            writer.WriteStartElement ( NAME );
            writer.WriteString ( this.Name );
            writer.WriteEndElement ();
            writer.WriteStartElement ( PARENT );
            if ( Parent != null )
            {
                writer.WriteAttributeString ( ELEMENT_EXISTMARK, ELEMENT_EXIST );
                writer.WriteStartElement ( NAME );
                writer.WriteString ( Parent.Name );
                writer.WriteEndElement ();
            }
            else
            {
                writer.WriteAttributeString ( ELEMENT_EXISTMARK, ELEMENT_NOTEXIST );
            }
            writer.WriteEndElement ();
            writer.Flush ();
            XmlSerializer xmlSer2 = XmlSerializer.FromTypes ( new Type[ ] { typeof ( NodeTestCollection ) } )[ 0 ];
            xmlSer2.Serialize ( writer, this.Children );
            writer.Flush ();
            writer.Flush ();
        }
    }

List<NodeTest>改成NodeTestCollection,解决序列化时结构错乱问题(本来打算细说的,算了,懒得打字了,看客自己试试就知道了),此改动,还很利于后续的优化扩展,比如名字索引等,实现如下:

    [Serializable]
    [XmlRoot(ElementName = "Children" )]
    
    public class NodeTestCollection : IList<NodeTest>, IXmlSerializable
    {
        private const string ROOT = "Children";
        private const string CHILDCOUNT = "ChildCount";

        #region 继承实现自IList<NodeTest>
        private IList<NodeTest> m_lstNodes = new List<NodeTest> ();

        public int IndexOf ( NodeTest item )
        {
            int iIdx = -1;

            for ( int i = 0 ; i < m_lstNodes.Count ; i++ )
            {
                if ( m_lstNodes[ i ] == item )
                {
                    iIdx = i;
                    break;
                }
            }

            return iIdx;
        }

        public void Insert ( int index, NodeTest item )
        {
            m_lstNodes.Insert ( index, item );
        }
        [XmlElement ( "NodeTest", Type = typeof ( NodeTest ) )]

        public NodeTest this[ int index ]
        {
            get
            {
                return m_lstNodes[ index ];
            }
            set
            {
                m_lstNodes[ index ] = value;
            }
        }

        public void Add ( NodeTest item )
        {
            m_lstNodes.Add ( item );
        }

        public bool Contains ( NodeTest item )
        {
            return m_lstNodes.Contains ( item );
        }

        public void CopyTo ( NodeTest[ ] array, int arrayIndex )
        {
            m_lstNodes.CopyTo ( array, arrayIndex );
        }

        public bool Remove ( NodeTest item )
        {
            return m_lstNodes.Remove ( item );
        }

        IEnumerator<NodeTest> IEnumerable<NodeTest>.GetEnumerator ()
        {
            return m_lstNodes.GetEnumerator ();
        }

        public void RemoveAt ( int index )
        {
            m_lstNodes.RemoveAt ( index );
        }

        public void Clear ()
        {
            m_lstNodes.Clear ();
        }

        public int Count
        {
            get { return m_lstNodes.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
        {
            return m_lstNodes.GetEnumerator ();
        }
        #endregion

        public System.Xml.Schema.XmlSchema GetSchema ()
        {
            return null;
        }

        public void ReadXml ( System.Xml.XmlReader reader )
        {
            //if ( reader.IsEmptyElement ) return;

            int iChildCount = 0;
            if ( reader.MoveToAttribute ( CHILDCOUNT ) ) iChildCount = int.Parse ( reader.GetAttribute ( CHILDCOUNT ) );

            reader.ReadStartElement ( ROOT );

            if (iChildCount > 0)
            {
                XmlSerializer xmlSer = XmlSerializer.FromTypes ( new Type[ ] { typeof ( NodeTest ) } )[ 0 ];
                for ( int i = 0 ; i < iChildCount ; i++ )
                {
                    var readerSub = reader.ReadSubtree();
                    this.Add ( ( NodeTest )xmlSer.Deserialize ( readerSub ) );
                    reader.ReadEndElement ();
                }
            }
        }

        public void WriteXml ( System.Xml.XmlWriter writer )
        {
            int iCnt = this.Count;
            writer.WriteAttributeString ( CHILDCOUNT, iCnt.ToString () );

            XmlSerializer xmlSer = XmlSerializer.FromTypes ( new Type[ ] { typeof ( NodeTest ) } )[ 0 ];
            for ( int i = 0 ; i < iCnt ; i++ )
            {
                xmlSer.Serialize ( writer, this[ i ] );
            }
        }
    }
  • 思路解析

说说思路:改造的地方不多,主要是继承方法的实现上,也就是ReadXml WriteXml方法实现上。在实现它们时,采用一点手段,在方法体类对循环引用的对象进行区别处理。

思路

原理很简单,循环引用的地方,在本例中即Parent对象,采用简单存储,存储一个指引信息,这里为图简洁,直接使用Name属性作为指引【后续各位观众具体使用是可以使用唯一标识符进行优化,在此我就不改了】。将所有NodeTest对象信息存为字典,在反序列化时使用指引进行挂接,因为这是class,是引用对象,简单一改,全部挂接完成,挂接处就是For循环处。

此外还有两处XmlAttribute——ExistMark和ChildCount,分别用来辅助Parent存在时检测以及子节点存在检测。

采用这个方法,序列化出来如下图:

 

反序列化也成功进行挂接,不信,你试试。

原理就这么简单。

 不懂就留言吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/383781.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot之@ConfigurationProperties、@EnableConfigurationProperties

ConfigurationProperties 这个注解不仅可以为yml某个类注入还可以为第三方bean绑定属性 为yml某个类注入 只要将对应的yml类对象声明实体pojo并交给spring容器管理&#xff0c;再在类上使用ConfigurationProperties绑定对应的类名即可 涉及到两个知识点&#xff0c;这个类对…

C语言中的数据储存规则

写在开头 关于复习的相关内容其实从一开始就列出了大纲&#xff0c;但是迟迟没有开始复习&#xff0c;一方面是因为学校学业却是繁忙&#xff0c;另一方面还是内心对旧知识掌握不熟练需要再学一遍的畏惧和懒惰&#xff0c;但如今&#xff0c;复习必须开始了。今天我从C语言的最…

Linux/MacOS 生成双击可执行文件

双击可执行文件包含两种&#xff1a;终端shell脚本 Unix可执行文件 1.终端shell脚本 随意新建一个文件&#xff08;可使用command键N&#xff0c;前提是有已打开的文件&#xff09;&#xff0c;输入shell格式的测试代码&#xff0c;比如&#xff1a; #! /bin/sh echo “h…

双喜临门|炼石荣获2023年中国网络和数据安全高峰论坛双奖项

2023年2月23日-24日&#xff0c;工业和信息化部、四川省人民政府联合主办以“新征程 新思路 高质量发展”为主题的“2023年中国网络和数据安全产业高峰论坛”在成都隆重召开。工信安全中心第一届网络安全高成长性企业“勇攀之星”正式揭晓&#xff0c;炼石以高成长性、高创新性…

【vue】iframe相关问题

一、刷新iframe页面iframe的地址没有改变的话&#xff0c;每打开一次iframe页面&#xff0c;都不会主动更新页面的。以下有几种方法&#xff0c;都可以实现&#xff0c;每打开一次页面&#xff0c;就刷新一下给iframe添加key<template><div id"Iframe">&…

一名IC验证工程师的成长路径是怎么样的?来听听工程师的见解

IC验证这个岗位对于非科班的学生是比较友好的&#xff0c;因为验证需要具备的技能UVM&#xff0c;SV&#xff0c;C等&#xff0c;非科班和科班的差距不会拉开太大。因其岗位需求量巨大而格外受到了大家的青睐&#xff0c;甚至成为不少学生的转行首选。 验证对于IC的重要性 IC…

汽车 12V 和 24V 电池输入保护推荐

简介汽车电池电源线路在运行系统时容易出现瞬变。所需的典型保护包括过压、过载、反极性和跨接启动。在汽车 的生命周期中&#xff0c;交流发电机可能会被更换为非OEM 部件。售后市场上的交流发电机可能具有不同的负载突降&#xff08;LOAD DUMP&#xff09;保护或没有负载突降…

论文阅读-Attention Bottlenecks for Multimodal Fusion(多模态特征融合)

一、论文信息 论文题目&#xff1a;Attention Bottlenecks for Multimodal Fusion paperwithcode&#xff1a;https://paperswithcode.com/paper/attention-bottlenecks-for-multimodal-fusion Github&#xff1a;https://github.com/google-research/scenic/tree/main/scen…

产品故事:语雀两度生死局

语雀是一款文档和知识库产品&#xff0c;2016 年从一个技术团队支付宝体验技术部生长出来&#xff0c;2021 年蚂蚁成立了智能协同事业部&#xff0c;重点产品即为语雀&#xff0c;以独立 BU 运作&#xff0c;算是完成了“成人礼”。我们和玉伯聊了聊语雀的成长故事。 极客时间&…

C++基础知识【3】控制语句

目录 前言 一、条件语句 1.1、if 语句 1.2、if-else 语句 1.3、switch 语句 二、循环语句 2.1、while 循环 2.2、do-while 循环 2.3、for 循环 三、跳转语句 3.1、break语句 3.2、continue语句 3.3、goto语句 四、一些新特性 4.1、if 语句和 switch 语句…

【数据结构与算法】图遍历算法 ( 深度优先搜索代码示例 )

文章目录一、深度优先搜索算法二、完整代码示例完整代码示例执行结果一、深度优先搜索算法 深度优先搜索算法步骤 : 将 深度优先搜索 算法步骤 转为代码 ; ① 访问初始结点 : 访问 初始结点 v , 并将该 初始结点 v 标记为 " 已访问 " ; 设置一个 访问标记 数组 , 数…

《C++ Primer》 第九章 顺序容器

《C Primer》 第九章 顺序容器 9.1 顺序容器概述 容器&#xff1a;特定类型对象的集合 顺序容器类型 vector 可变大小数组&#xff0c;支持快速随机访问&#xff0c;在尾部之外的位置插入或删除元素可能很慢deque 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快…

【2022-09-14】米哈游秋招笔试三道编程题

第一题&#xff1a;最短子串 题目描述 米小游拿到了一个字符串&#xff0c;她想截取一个连续子串&#xff0c;使得该子串中包含至少k个连续的“mihoyo”。 你可以帮米小游求出最短的子串长度&#xff0c;以及对应的子串位置吗&#xff1f; 输入描述 第一行输入两个正整数n…

产品父子流程技术方案设计

产品父子流程技术方案设计 一、整体设计 根据业务需求分析&#xff0c;产品涉及法人代表及实控人风控决策流程调用&#xff0c;旨在降低风险&#xff0c;提高行内线上贷款业务风险决策的能力。 二、业务流程 1.业务流程图 2.交易流程 在授信交易切面入口处对法人代表及实控…

Spark性能优化三 checkpoint

&#xff08;一&#xff09;checkpoint介绍 checkpoint&#xff0c;是Spark提供的一个比较高级的功能。有时候&#xff0c;我们的Spark任务&#xff0c;比较复杂&#xff0c;从初始化RDD开始&#xff0c;到最后整个任务完成&#xff0c;有比较多的步骤&#xff0c;比如超过10个…

关于flex盒子padding-right/margin-right不生效

错误代码实例&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"width…

论文投稿指南——中文核心期刊推荐(科学、科学研究)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

element input 输入框校验

element input 输入框校验 很久之前有写过一篇&#xff1a;element 输入框只可以输入正整数 在这里对它进行补充 校验input输入框是否符合 以下是常用的一些&#xff1a; 限制输入中文&#xff1a;/^[\u4e00-\u9fa5]$/ const checkName (rule, value, callback) > {if…

逆约瑟夫问题

约瑟夫问题可以说十分经典&#xff0c;其没有公式解也是广为人知的~ 目录 前言 一、约瑟夫问题与逆约瑟夫问题 1.约瑟夫问题 2.逆约瑟夫问题 二、思考与尝试&#xff08;显然有很多失败&#xff09; 问题分析 尝试一&#xff1a;递归/递推的尝试 尝试二&#xff1a;条件…

Doris入门篇-分区分桶实验

简介 测试分区分桶效果。 分区的基本操作 添加分区 ALTER TABLE v2x_olap_database.government_car ADD PARTITION p20221203 VALUES LESS THAN ("2022-12-04");动态分区表不能添加分区&#xff0c;需要转为手动分区表。 查看分区 show partitions from <表…