使用自定义XmlSerialization反序列化复杂对象

时间:2015-04-09 10:56:31

标签: c# .net xml-serialization xmlserializer

我编写了以下示例代码,将稍微复杂的对象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或任何自行处理所有内容的框架是否有任何简单的反序列化方法?

1 个答案:

答案 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字段)。事情变得棘手:引用本质上是一个记忆地址。因此,将序列化它的价值并将其再次反序列化是毫无意义的。因为对象现在最可能位于另一个内存位置,所以反序列化的地址将完全错误。

基本上有两种解决方法:

  1. 当对象被构造或粘合在一起时,可以以自动创建这些引用的方式构造序列化对象。这样就不需要序列化和反序列化。缺点是:这只能用于以这种方式获得的引用(在当前示例中就是这种情况)

  2. 每个可以作为引用目标的对象都可以通过标识符字段进行扩展,这与数据库中的主键非常相似。然后将该标识符(例如guid)序列化和反序列化。每个referece字段(在此示例中为Member类的Parent字段)将被序列化为它引用的对象的标识符值(可以通过添加辅助字段ParentID来完成,该字段由Parent字段的setter自动设置) 。当所有内容都被反序列化时,必须通过遍历整个对象树来重建这些引用。从好的方面来说,这使得人们可以重建任意引用。但是必须要注意这一点,这会给代码增加一些真正的复杂性。

  3. 第一种方法可以通过以下方式完成:

    在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; }
    }
    

    在第二个例子中披露了您对原始问题的补充的概念证明。