XmlSerializer.Deserialize() 不能正确反序列化 List<T>。

5
我对C#的XmlSerializer还不熟悉,可能会错过一些基本的东西。
我的问题是,我有一个类,其中包含另一个类的List。当我序列化主类时,XML看起来很漂亮,所有数据都完好无损。但是当我反序列化XML时,List中的数据消失了,只剩下一个空的List。我没有收到任何错误消息,而且序列化部分工作得很好。
请问我在反序列化过程中错过了什么?
注:下面的代码未能复现该问题,它可以正常工作。这只是真实代码的简化版本,无法复现该问题!
public class User
{
  public User()
  {
    this.Characters = new List<Character>();
  }
  public string Username { get; set; }
  public List<Character> Characters { get; set; }
}

public class Character
{
  public Character()
  {
    this.Skills = new List<Skill>();
  }
  public string Name { get; set; }
  public List<Skill> Skills { get; set; }
}

public enum Skill
{
  TreeClimber,
  ForkliftOperator
}

public static void Save(User user)
{
    using (var textWriter = new StreamWriter("data.xml"))
    {
        var xmlSerializer = new XmlSerializer(typeof(User));
        xmlSerializer.Serialize(textWriter, user);
    }
}

public static User Restore()
{
    if (!File.Exists("data.xml"))
        throw new FileNotFoundException("data.xml");

    using (var textReader = new StreamReader("data.xml"))
    {
        var xmlSerializer = new XmlSerializer(typeof(User));
        return (User)xmlSerializer.Deserialize(textReader);
    }
}

public void CreateAndSave()
{
  var character = new Character();
  character.Name = "Tranzor Z";
  character.Skills.Add(Skill.TreeClimber);

  var user = new User();
  user.Username = "Somebody";
  user.Characters.Add(character);

  Save(user);
}

public void RestoreAndPrint()
{
  var user = Restore();
  Console.WriteLine("Username: {0}", user.Username);
  Console.WriteLine("Characters: {0}", user.Characters.Count);
}

执行CreateAndSave()生成的XML如下所示:
<User>
  <Username>Somebody</Username>
  <Characters>
    <Character>
      <Name>Tranzor Z</Name>
      <Skills>
        <Skill>TreeClimber</Skill>
      </Skills>
    </Character>
  <Characters>
</User>

太好了!这就是应该的样子。如果我执行RestoreAndPrint(),我会得到一个用户对象,其中用户名属性设置正确,但角色属性为空列表:

Username: Somebody
Characters: 0

有人能解释一下为什么Characters属性可以正确序列化,但无法反序列化吗?

3
你不需要在类型上加 [Serializable] 标记。Xml Serializer 的行为完全独立于该属性。 - Cheeso
好的,既然我们(或者至少是我)怀疑问题出在“真正”的代码中(未显示),你在列表的“set”访问器中做了什么有趣的事情吗?那将是我首先查看的地方。 - Marc Gravell
不,它们都是基本属性,在get或set访问器中没有任何花哨的东西。它们都长得像这样:public T PropertyName { get; set; } - Andy
即使是针对列表?例如:public List<T> PropName {get;set;}? - Marc Gravell
即使对于列表也是如此。然而,我之所以在列表上有公共的设置器,只是为了反序列化器。如果设置器是私有的,那么XmlSerializer会抛出一个InvalidOperationException,抱怨只读的Characters属性。 - Andy
8个回答

5

无法重现;在修复了更紧急的错误后,我得到了以下结果:

Username: Somebody
Characters: 1

变更:

  • 使用WriteLine代替WriteFormat(后者会导致编译失败)
  • 在默认构造函数中初始化列表(这可以避免CreateAndSave出现问题):
    • public User() { Characters = new List<Character>(); }
    • public Character() { Skills = new List<Skill>(); }

1
嗯...我上面发布的代码是模仿我的真实代码的示例代码,因此出现了错误,对此感到抱歉。我的实际项目要复杂得多,但发布的代码相当接近我想要做的事情。我的意图是提供一个代表我的情况的示例,而不提供所有细节。 :/ - Andy
很不幸,我认为问题在被省略的代码中。 - Marc Gravell
嘿,我会看一下protobuf-net,但是我的这个项目的目标是一个可读/可编辑的格式,可以用任何基本文本编辑器进行操作。 - Andy

2

以前,在序列化列表时,我使用了[XmlArray]和[XmlArrayItem]注释。然后,您可以在Characters属性上放置一个[XmlIgnore]注释。在您的情况下,它应该类似于这样:

[XmlArray("Characters")]
[XmlArrayItem("Character", Type=typeof(Character))]
public Character[] _ Characters
{
    get
    {
        //Make an array of Characters to return 
        return Characters.ToArray();
    }

    set
    {
        Characters.Clear();
        for( int i = 0; i < value.Length; i++ )
            Characters.Add( value[i] );
    }
}

希望这能帮到您。

没有运气——我得到了相同的结果,即一个空列表。当我反序列化时,没有任何与角色相关的代码被触及。就好像整个XML中的Characters节点被丢弃而没有被处理一样。 - Andy

2

我知道这已经很老了,但我也遇到了同样的问题。

我发现如果序列化图中有实现IXmlSerializable接口的对象,它会破坏所有反序列化。列表不再反序列化,也不会反序列化我所说的对象。序列化工作正常。


3
发现原因:在ReadXml方法中要注意将读取器(reader)推进到结束标记(closing tag)。 - alexmage
1
如果您已经实现了IXmlSerializable,那么以下是非常重要的观察 - 确保在自定义xml读取器逻辑的末尾使用reader.ReadEndElement();。 - Mark Mullin
真希望我能给你们俩点赞十次...为什么我从MSDN文章中逐字复制代码,还是出现了这个错误? - vines

1
也许,您可以尝试使用XmlReader而非StreamReader。此外,作为排除故障的步骤,您可以尝试使用显式类型(如下所示)而不是“var”声明来运行现有代码。
public static User Restore()
{
  if (!File.Exists("data.xml"))
    throw new FileNotFoundException("data.xml");

  XmlReader xr = XmlReader.Create("data.xml");
  XmlSerializer serializer = new XmlSerializer(typeof(User));
  var user = (User)serializer.Deserialize(xr);
  xr.Close();
  return user;
}

编辑: 此外,尝试在您的用户类上使用XmlInclude注释。

[XmlInclude( typeof( Character ) )]

我已经在所有相关的类中添加了XmlIncludeAttributes,但反序列化仍然失败。 - Andy

1

在长时间的代码调试后,我最终放弃了使用XmlSerializer的默认行为的想法。

现在所有相关的类都继承IXmlSerializer并实现ReadXml()和WriteXml()函数。我真的很希望能够采取懒惰的方式,但是现在我已经尝试了IXmlSerializer,我意识到它真的不难,而且增加的灵活性真的很好。


0
我将这个属性添加到我的列表类型中,然后它就可以工作了(遇到了和你一样的问题):
[System.Xml.Serialization.XmlElementAttribute("NameOfMyField")]
public List<SomeType> NameOfMyField{get;set;}

我认为这是你的解决方案。


0
Stream stream = File.Open(filename + ".xml", FileMode.Open);
User user = null;
using (XmlReader reader = XmlReader.Create(stream))
{
    user = IntermediateSerializer.Deserialize<User>(reader, null);
}
stream.Close();

return user;

我会尝试使用类似这样的东西。

我正在处理的代码与游戏相关,但在这个项目中我没有引用XNA框架,因此无法访问IntermediateSerializer。 - Andy
抱歉,我做出了假设。 - Chris Watts

-2

这可能与枚举的反序列化有关...因为它将其序列化为字符串值...可能会在创建字符的正确枚举值时遇到问题。尚未测试此假设...


在我的实际代码项目中,根类确实有其他枚举类型,它们被正确地序列化和反序列化。我也可以成功地序列化和反序列化Characters列表中的单个对象。只有在反序列化用户对象时,该过程才会失败,并且Characters列表为空,没有任何异常被抛出。 - Andy
1
-1:枚举没有问题。我建议将来要么进行测试,要么保持沉默。 - John Saunders

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接