我编写了以下示例代码,将稍微复杂的对象FamilyTreeFile
保存到XML并将其恢复为原始格式。
public class XmlSerializationTest
{
const string FileName = @"FamilyTree.xml";
public void Run()
{
var rootMember = new Member() { Name = "Johny", Parent = null };
var member1 = new Member() { Name = "Andy", Parent = rootMember };
var member2 = new Member() { Name = "Adam", Parent = rootMember };
var member3 = new Member() { Name = "Andrew", Parent = rootMember };
var member4 = new Member() { Name = "Davis", Parent = member2 };
var member5 = new Member() { Name = "Simon", Parent = member4 };
rootMember.FamilyTree = new GenericCollection();
rootMember.FamilyTree.Add(member1);
rootMember.FamilyTree.Add(member2);
rootMember.FamilyTree.Add(member3);
member2.FamilyTree = new GenericCollection();
member2.FamilyTree.Add(member4);
member4.FamilyTree = new GenericCollection();
member4.FamilyTree.Add(member5);
var familyTree = new GenericCollection() { rootMember };
IFamilyTreeFile file = new FamilyTreeFile()
{
FamilyTree = familyTree
};
Serialize(file);
file = Deserialize();
}
public void Serialize(IFamilyTreeFile obj)
{
var xmlSerializer = new XmlSerializer(typeof(FamilyTreeFile));
using (TextWriter writer = new StreamWriter(FileName))
{
xmlSerializer.Serialize(writer, obj);
}
}
public IFamilyTreeFile Deserialize()
{
XmlSerializer serializer = new XmlSerializer(typeof(FamilyTreeFile));
using (Stream stream = File.Open(FileName, FileMode.Open))
{
return (IFamilyTreeFile)serializer.Deserialize(stream);
}
}
}
public interface IMember
{
string Name { get; set; }
IMember Parent { get; set; }
GenericCollection FamilyTree { get; set; }
}
[Serializable]
public class Member : IMember
{
[XmlAttribute]
public string Name { get; set; }
[XmlIgnore]
public IMember Parent { get; set; }
public GenericCollection FamilyTree { get; set; }
public Member()
{
//FamilyTree = new GenericCollection();
}
}
[Serializable]
public class GenericCollection : List<IMember>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
if (reader.Name == "FamilyTree")
{
do
{
reader.Read();
if (reader.Name == "Member" && reader.IsStartElement())
{
Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.Name == reader.Name)
.FirstOrDefault();
if (type != null)
{
var xmlSerializer = new XmlSerializer(type);
var member = (IMember)xmlSerializer.Deserialize(reader);
this.Add(member);
}
}
if (reader.Name == "FamilyTree" && reader.NodeType == System.Xml.XmlNodeType.EndElement)
break;
}
while (!reader.EOF);
}
}
public void WriteXml(XmlWriter writer)
{
foreach (IMember rule in this)
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add(String.Empty, String.Empty);
XmlSerializer xmlSerializer = new XmlSerializer(rule.GetType());
xmlSerializer.Serialize(writer, rule, namespaces);
}
}
}
public interface IFamilyTreeFile
{
GenericCollection FamilyTree { get; set; }
}
public class FamilyTreeFile : IFamilyTreeFile
{
public GenericCollection FamilyTree { get; set; }
}
代码示例生成以下XML文件,该文件完全符合我的需要,但我无法使用ReadXml方法将其读回。
<?xml version="1.0" encoding="utf-8"?>
<FamilyTreeFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FamilyTree>
<Member Name="Johny">
<FamilyTree>
<Member Name="Andy" />
<Member Name="Adam">
<FamilyTree>
<Member Name="Davis">
<FamilyTree>
<Member Name="Simon" />
</FamilyTree>
</Member>
</FamilyTree>
</Member>
<Member Name="Andrew" />
</FamilyTree>
</Member>
</FamilyTree>
</FamilyTreeFile>
我需要帮助才能有效地恢复它?
ADDED
在Notes
IMember
public interface IMember
{
string Name { get; set; }
IMember Parent { get; set; }
GenericCollection FamilyTree { get; set; }
List<Note> Notes { get; set; }
}
[Serializable]
public class Note
{
[XmlAttribute]
public string Text { get; set; }
}
在Member
class
[XmlArray("Notes")]
public List<Note> Notes { get; set; }
我无法在此行反序列化Notes信息。
var member = (IMember)xmlSerializer.Deserialize(reader);
使用XmlSerializer或任何自行处理所有内容的框架是否有任何简单的反序列化方法?
答案 0 :(得分:2)
以下是GenericCollection.ReadXml方法的工作版本:
public void ReadXml(XmlReader reader)
{
// no need to advace upfront so MoveToContent was taken out (would
// mess with subsequent inner deserializations anyway)
// very important: there may be no members, so check IsEmptyElement
if (reader.Name == "FamilyTree" && !reader.IsEmptyElement)
{
do
{
if (reader.Name == "Member" && reader.IsStartElement())
{
Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.Name == reader.Name)
.FirstOrDefault();
if (type != null)
{
var xmlSerializer = new XmlSerializer(type);
var member = (IMember) xmlSerializer.Deserialize(reader);
this.Add(member);
}
continue; // to omit .Read because Deserialize did already
// advance us to next element
}
if (reader.Name == "FamilyTree" && reader.NodeType == XmlNodeType.EndElement)
break;
reader.Read();
} while (!reader.EOF);
}
}
您最有可能错过的版本是,每次调用XmlReader方法(例如Read...()
或Move...()
)都会提升它的阅读位置。成员对象的内部反序列化也是如此。
牢记这一点,应该清楚的是,你不能总是在循环开始时发出Read()
,而只是在最后。这样你可以使用continue
关键字跳过它,以防循环体中的其他代码(在我们的例子中为Deserialize()
)已经推进了XmlReader。同样适用于您的方法版本开头的MoveToContent()
。我最初也错过了,是成员集合可能是空的。在这种情况下,必须完全省略GenericCollection的反序列化,因为(再次)不要弄乱读者。
虽然这会对对象实例进行反序列化并将它们添加到各自的列表中,但不会重建引用(本例中的Member类的Parent字段)。事情变得棘手:引用本质上是一个记忆地址。因此,将序列化它的价值并将其再次反序列化是毫无意义的。因为对象现在最可能位于另一个内存位置,所以反序列化的地址将完全错误。
基本上有两种解决方法:
当对象被构造或粘合在一起时,可以以自动创建这些引用的方式构造序列化对象。这样就不需要序列化和反序列化。缺点是:这只能用于以这种方式获得的引用(在当前示例中就是这种情况)
每个可以作为引用目标的对象都可以通过标识符字段进行扩展,这与数据库中的主键非常相似。然后将该标识符(例如guid)序列化和反序列化。每个referece字段(在此示例中为Member类的Parent字段)将被序列化为它引用的对象的标识符值(可以通过添加辅助字段ParentID来完成,该字段由Parent字段的setter自动设置) 。当所有内容都被反序列化时,必须通过遍历整个对象树来重建这些引用。从好的方面来说,这使得人们可以重建任意引用。但是必须要注意这一点,这会给代码增加一些真正的复杂性。
第一种方法可以通过以下方式完成:
在Run()函数中更改此内容...
var rootMember = new Member() { Name = "Johny"};
var member1 = new Member() { Name = "Andy" };
var member2 = new Member() { Name = "Adam" };
var member3 = new Member() { Name = "Andrew" };
var member4 = new Member() { Name = "Davis" };
var member5 = new Member() { Name = "Simon" };
...更改属性FamilyTree of class Member to this ...
public GenericCollection FamilyTree
{
get { return _FamilyTree; }
set
{
_FamilyTree = value;
_FamilyTree.Owner = this;
}
}
...并将其插入到GenericCollection类
中private IMember _Owner;
public IMember Owner
{
get { return _Owner; }
set
{
_Owner = value;
foreach (var member in this)
{
member.Parent = value;
}
}
}
public void Add(IMember item)
{
item.Parent = Owner;
base.Add(item);
}
第二种方法在以下小型控制台应用程序中实现:
class Program
{
public static string FileName = @"FamilyTree.xml";
static void Main(string[] args)
{
// make some members
var rootMember = new Member() { Name = "Johny" };
var member1 = new Member() { Name = "Andy" };
var member2 = new Member() { Name = "Adam" };
var member3 = new Member() { Name = "Andrew" };
var member4 = new Member() { Name = "Davis" };
var member5 = new Member() { Name = "Simon" };
// construct some arbitrary references between them
member1.Reference = member4;
member3.Reference = member1;
member5.Reference = member2;
// let member 3 have some notes
member3.Notes = new List<Note>();
member3.Notes.Add(new Note() { Text = "note1" });
member3.Notes.Add(new Note() { Text = "note2" });
// add all of the to the family tree
rootMember.FamilyTree.Add(member1);
rootMember.FamilyTree.Add(member2);
rootMember.FamilyTree.Add(member3);
member2.FamilyTree.Add(member4);
member4.FamilyTree.Add(member5);
var familyTree = new GenericCollection() { rootMember };
IFamilyTreeFile file = new FamilyTreeFile()
{
FamilyTree = familyTree
};
Console.WriteLine("--- input ---");
Serialize(file);
PrintTree(file.FamilyTree, 0);
Console.WriteLine();
Console.WriteLine("--- output ---");
file = Deserialize();
file.FamilyTree.RebuildReferences(file.FamilyTree); // this is where the refereces
// are put together again after deserializing the object tree.
PrintTree(file.FamilyTree, 0);
Console.ReadLine();
}
private static void PrintTree(GenericCollection c, int indent)
{
foreach (var member in c)
{
string line = member.Name.PadLeft(indent, ' ');
if (member.Reference != null)
{
line += " (Ref: " + member.Reference.Name + ")";
if (member.Notes != null && member.Notes.Count > 0)
{
line += " (Notes: ";
foreach (var note in member.Notes)
{
line += note.Text + ",";
}
line += ")";
}
}
Console.WriteLine(line);
PrintTree(member.FamilyTree, indent + 4);
}
}
public static void Serialize(IFamilyTreeFile obj)
{
var xmlSerializer = new XmlSerializer(typeof(FamilyTreeFile));
using (TextWriter writer = new StreamWriter(FileName))
{
xmlSerializer.Serialize(writer, obj);
}
}
public static IFamilyTreeFile Deserialize()
{
XmlSerializer serializer = new XmlSerializer(typeof(FamilyTreeFile));
using (Stream stream = File.Open(FileName, FileMode.Open))
{
return (IFamilyTreeFile)serializer.Deserialize(stream);
}
}
}
public interface IMember
{
Guid ID { get; set; }
string Name { get; set; }
IMember Reference { get; set; }
Guid ReferenceID { get; set; }
GenericCollection FamilyTree { get; set; }
List<Note> Notes { get; set; }
void RebuildReferences(GenericCollection in_Root);
}
[Serializable]
public class Member : IMember
{
private GenericCollection _FamilyTree;
private IMember _Reference;
[XmlAttribute]
public Guid ID { get; set; }
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public Guid ReferenceID { get; set; }
[XmlIgnore]
public IMember Reference
{
get { return _Reference; }
set
{
ReferenceID = value.ID;
_Reference = value;
}
}
[XmlArray("Notes")]
public List<Note> Notes { get; set; }
public GenericCollection FamilyTree
{
get { return _FamilyTree; }
set
{
_FamilyTree = value;
_FamilyTree.Owner = this;
}
}
public Member()
{
ID = Guid.NewGuid();
FamilyTree = new GenericCollection();
}
public void RebuildReferences(GenericCollection in_Root)
{
if (!ReferenceID.Equals(Guid.Empty))
Reference = in_Root.FindMember(ReferenceID);
FamilyTree.RebuildReferences(in_Root);
}
}
[Serializable]
public class Note
{
[XmlAttribute]
public string Text { get; set; }
}
[Serializable]
public class GenericCollection : List<IMember>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
private IMember _Owner;
public IMember Owner
{
get { return _Owner; }
set
{
_Owner = value;
}
}
public void Add(IMember item)
{
base.Add(item);
}
public void ReadXml(XmlReader reader)
{
// no need to advace upfront so MoveToContent was taken out (would
// mess with subsequent inner deserializations anyway)
// very important: there may be no members, so check IsEmptyElement
if (reader.Name == "FamilyTree" && !reader.IsEmptyElement)
{
do
{
if (reader.Name == "Member" && reader.IsStartElement())
{
Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.Name == reader.Name)
.FirstOrDefault();
if (type != null)
{
var xmlSerializer = new XmlSerializer(type);
var member = (IMember)xmlSerializer.Deserialize(reader);
this.Add(member);
}
continue; // to omit .Read because Deserialize did already
// advance us to next element
}
if (reader.Name == "FamilyTree" && reader.NodeType == XmlNodeType.EndElement)
break;
reader.Read();
} while (!reader.EOF);
}
}
public void WriteXml(XmlWriter writer)
{
foreach (IMember rule in this)
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add(String.Empty, String.Empty);
XmlSerializer xmlSerializer = new XmlSerializer(rule.GetType());
xmlSerializer.Serialize(writer, rule, namespaces);
}
}
public void RebuildReferences(GenericCollection in_Root)
{
foreach (IMember meber in this)
{
meber.RebuildReferences(in_Root);
}
}
public IMember FindMember(Guid in_ID)
{
IMember FoundMember = null;
foreach (IMember member in this)
{
if (member.ID.Equals(in_ID))
return member;
FoundMember = member.FamilyTree.FindMember(in_ID);
if (FoundMember != null)
return FoundMember;
}
return null;
}
}
public interface IFamilyTreeFile
{
GenericCollection FamilyTree { get; set; }
}
public class FamilyTreeFile : IFamilyTreeFile
{
public GenericCollection FamilyTree { get; set; }
}
在第二个例子中披露了您对原始问题的补充的概念证明。