使用自定义 XmlSerialization 反序列化复杂对象
Deserialize complex object using custom XmlSerialization
我编写了以下示例代码来将稍微复杂的对象 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>
我需要帮助才能有效恢复它?
已添加
在 IMember
中添加新集合 Notes
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 或任何自行处理一切的框架进行反序列化?
这是适合您的 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...()
)都会提高其阅读位置。成员 objects 的内部反序列化也是如此。
牢记这一点,很明显您不能总是在循环开始时发出 Read()
,而只能在循环结束时发出。这样您就可以使用 continue
关键字跳过它,以防循环体中的某些其他代码(如本例中的 Deserialize()
)已经推进了 XmlReader。同样适用于您的方法版本开头的 MoveToContent()
。我最初也确实错过了成员集合可以为空的事实。在那种情况下,必须完全省略 GenericCollection 的反序列化,因为(再次)不要弄乱 reader.
虽然这会反序列化 object 实例并将它们添加到各自的列表中,但不会重建引用(本例中成员 class 的 Parent 字段)。这就是事情变得棘手的地方:引用本质上是一个内存地址。正因为如此,序列化它的价值并再次反序列化它是没有意义的。因为 objects 现在很可能驻留在另一个内存位置,所以反序列化地址将是完全错误的。
基本上有两种方法可以解决这个问题:
序列化的 object 可以以一种自动创建这些引用的方式构建,当 object 被构建或粘合在一起时。这样就根本不需要序列化和反序列化。缺点是:这仅适用于可以通过这种方式获得的引用(当前示例中就是这种情况)
每个可以作为引用目标的 object 都可以通过标识符字段进行扩展,这与数据库中的主键非常相似。该标识符(例如 guid)随后将被序列化和反序列化。每个引用字段(本例中成员 class 的 Parent 字段)将被序列化为它引用的 object 的标识符值(可以通过添加辅助字段 ParentID,由Parent字段的setter自动设置)。当一切都被反序列化时,这些引用必须通过遍历 object 的整个树来重建。从好的方面来说,这使人们能够重建任意引用。但是必须意识到这给代码增加了一些真正的复杂性。
第一种方法可以通过以下方式完成:
正在 运行() 函数中更改此...
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" };
...将 class 成员的 属性 家谱更改为此...
public GenericCollection FamilyTree
{
get { return _FamilyTree; }
set
{
_FamilyTree = value;
_FamilyTree.Owner = this;
}
}
... 并将其插入 class 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; }
}
在第二个示例中公开了您添加到原始问题的概念证明。
我编写了以下示例代码来将稍微复杂的对象 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>
我需要帮助才能有效恢复它?
已添加
在 IMember
Notes
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 或任何自行处理一切的框架进行反序列化?
这是适合您的 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...()
)都会提高其阅读位置。成员 objects 的内部反序列化也是如此。
牢记这一点,很明显您不能总是在循环开始时发出 Read()
,而只能在循环结束时发出。这样您就可以使用 continue
关键字跳过它,以防循环体中的某些其他代码(如本例中的 Deserialize()
)已经推进了 XmlReader。同样适用于您的方法版本开头的 MoveToContent()
。我最初也确实错过了成员集合可以为空的事实。在那种情况下,必须完全省略 GenericCollection 的反序列化,因为(再次)不要弄乱 reader.
虽然这会反序列化 object 实例并将它们添加到各自的列表中,但不会重建引用(本例中成员 class 的 Parent 字段)。这就是事情变得棘手的地方:引用本质上是一个内存地址。正因为如此,序列化它的价值并再次反序列化它是没有意义的。因为 objects 现在很可能驻留在另一个内存位置,所以反序列化地址将是完全错误的。
基本上有两种方法可以解决这个问题:
序列化的 object 可以以一种自动创建这些引用的方式构建,当 object 被构建或粘合在一起时。这样就根本不需要序列化和反序列化。缺点是:这仅适用于可以通过这种方式获得的引用(当前示例中就是这种情况)
每个可以作为引用目标的 object 都可以通过标识符字段进行扩展,这与数据库中的主键非常相似。该标识符(例如 guid)随后将被序列化和反序列化。每个引用字段(本例中成员 class 的 Parent 字段)将被序列化为它引用的 object 的标识符值(可以通过添加辅助字段 ParentID,由Parent字段的setter自动设置)。当一切都被反序列化时,这些引用必须通过遍历 object 的整个树来重建。从好的方面来说,这使人们能够重建任意引用。但是必须意识到这给代码增加了一些真正的复杂性。
第一种方法可以通过以下方式完成:
正在 运行() 函数中更改此...
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" };
...将 class 成员的 属性 家谱更改为此...
public GenericCollection FamilyTree
{
get { return _FamilyTree; }
set
{
_FamilyTree = value;
_FamilyTree.Owner = this;
}
}
... 并将其插入 class 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; }
}
在第二个示例中公开了您添加到原始问题的概念证明。