.net XML 序列化 - 存储引用而不是对象副本

Posted

技术标签:

【中文标题】.net XML 序列化 - 存储引用而不是对象副本【英文标题】:.net XML Serialization - Storing Reference instead of Object Copy 【发布时间】:2010-12-09 16:56:41 【问题描述】: 在 .Net/C# 应用程序中,我有相互引用的数据结构。 当我对它们进行序列化时,.Net 使用单独的对象副本对所有引用进行序列化。 在以下示例中,我尝试序列化为“Person”数组

“人”可能指代另一个人。

public class Person

    public string Name;
    public Person Friend;


Person p1 = new Person();
p1.Name = "John";

Person p2 = new Person();
p2.Name = "Mike";

p1.Friend = p2;

Person[] group = new Person[]  p1, p2 ;
XmlSerializer ser = new XmlSerializer(typeof(Person[]));
using (TextWriter tw = new StreamWriter("test.xml"))
    ser.Serialize(tw,group );

//above code generates following xml

<ArrayOfPerson>
  <Person>
    <Name>John</Name>
    <Friend>
      <Name>Mike</Name>
    </Friend>
  </Person>
  <Person>
    <Name>Mike</Name>
  </Person>
</ArrayOfPerson>

在上面的代码中,同一个“Mike”对象存在于两个地方,因为同一个对象有两个引用。

在反序列化时,它们变成了两个不同的对象,这不是它们被序列化时的确切状态。 我想避免这种情况,并且只有序列化 xml 中的对象副本,并且所有引用都应引用此副本。反序列化后,我想返回相同的旧数据结构。 有可能吗?

【问题讨论】:

刚刚发现 BinaryFormatter 和 SoapFormatter 保留了参考值。 SoapFormatter 已被弃用,它不支持泛型。 【参考方案1】:

XmlSerializer 是不可能的。您可以使用DataContractSerializer 使用PreserveObjectReferences 属性来实现此目的。你可以看看这个post,它解释了细节。

这是一个示例代码:

public class Person

    public string Name;
    public Person Friend;


class Program

    static void Main(string[] args)
    
        Person p1 = new Person();
        p1.Name = "John";

        Person p2 = new Person();
        p2.Name = "Mike";
        p1.Friend = p2;
        Person[] group = new Person[]  p1, p2 ;

        var serializer = new DataContractSerializer(group.GetType(), null, 
            0x7FFF /*maxItemsInObjectGraph*/, 
            false /*ignoreExtensionDataObject*/, 
            true /*preserveObjectReferences : this is where the magic happens */, 
            null /*dataContractSurrogate*/);
        serializer.WriteObject(Console.OpenStandardOutput(), group);
    

这会产生以下 XML:

<ArrayOfPerson z:Id="1" z:Size="2" xmlns="http://schemas.datacontract.org/2004/07/ToDelete" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Person z:Id="2">
        <Friend z:Id="3">
            <Friend i:nil="true"/>
            <Name z:Id="4">Mike</Name>
        </Friend>
        <Name z:Id="5">John</Name>
    </Person>
    <Person z:Ref="3" i:nil="true"/>
</ArrayOfPerson>

现在在构造函数中将PreserveObjectReferences 设置为false,你会得到这个:

<ArrayOfPerson xmlns="http://schemas.datacontract.org/2004/07/ToDelete" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Person>
        <Friend>
            <Friend i:nil="true"/>
            <Name>Mike</Name>
        </Friend>
        <Name>John</Name>
    </Person>
    <Person>
        <Friend i:nil="true"/>
        <Name>Mike</Name>
    </Person>
</ArrayOfPerson>

值得一提的是,这种方式生成的 XML 不可互操作,只能使用 DataContractSerializer 进行反序列化(与BinaryFormatter 相同)。

【讨论】:

【参考方案2】:

您可以使用 ExtendedXmlSerializer。下面是一个序列化的例子object reference and circular reference

如果你有课:

public class Person

    public int Id  get; set; 

    public string Name  get; set; 

    public Person Boss  get; set; 


public class Company

    public List<Person> Employees  get; set; 

然后你用循环引用创建对象,像这样:

var boss = new Person Id = 1, Name = "John";
boss.Boss = boss; //himself boss
var worker = new Person Id = 2, Name = "Oliver";
worker.Boss = boss;
var obj = new Company

    Employees = new List<Person>
    
        worker,
        boss
    
;

您必须将 Person 类配置为引用对象:

var serializer = new ConfigurationContainer().ConfigureType<Person>()
                                             .EnableReferences(p => p.Id)
                                             .Create();

最后你可以序列化你的对象:

var xml = serializer.Serialize(obj);

输出 xml 将如下所示:

<?xml version="1.0" encoding="utf-8"?>
<Company xmlns="clr-namespace:ExtendedXmlSerializer.Samples.ObjectReference;assembly=ExtendedXmlSerializer.Samples">
  <Employees>
    <Capacity>4</Capacity>
    <Person Id="2">
      <Name>Oliver</Name>
      <Boss Id="1">
        <Name>John</Name>
        <Boss xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:entity="1" />
      </Boss>
    </Person>
    <Person xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:entity="1" />
  </Employees>
</Company>

ExtendedXmlSerializer 支持 .net 4.5 和 .net Core。

【讨论】:

你应该提到你正在推广你自己的软件,这不是 .net 框架的一部分,虽然看起来确实是一项不错的工作

以上是关于.net XML 序列化 - 存储引用而不是对象副本的主要内容,如果未能解决你的问题,请参考以下文章

ASP.Net Web API Xml 序列化问题

值类型数组如何存储在 .NET 对象堆中?

Open-source Tutorial - Json.NET

ASP.NET Core中如何使用缓存

vb.net做对象xml序列化学习,程序报错

将const引用/指针传递给类进行存储的首选方法,而不是复制引用的对象