.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 序列化 - 存储引用而不是对象副本的主要内容,如果未能解决你的问题,请参考以下文章